diff --git a/.github/actions/install-apt-packages/action.yml b/.github/actions/install-apt-packages/action.yml deleted file mode 100644 index de5101707..000000000 --- a/.github/actions/install-apt-packages/action.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: 'install-apt-packages' -description: 'Install necessary apt packages' -runs: - using: "composite" - steps: - - id: install-apt-packages - run: | - sudo apt update && sudo apt -y install \ - doxygen \ - graphviz \ - imagemagick \ - python3 \ - python3-pip \ - python3-sphinxcontrib.spelling \ - python3-venv \ - software-properties-common \ - wget \ - libasio-dev \ - libtinyxml2-dev \ - clang-tidy \ - curl \ - grep - shell: bash diff --git a/.github/actions/project_dependencies/action.yml b/.github/actions/project_dependencies/action.yml new file mode 100644 index 000000000..39d825a4a --- /dev/null +++ b/.github/actions/project_dependencies/action.yml @@ -0,0 +1,55 @@ +name: fastdds_statistics_backend_dependencies_ubuntu +description: Common first step for all jobs. Checkout repository, download dependencies and install required packages. + +inputs: + + os: + description: Specify runs-on machine to download specific artifact + required: true + + cmake_build_type: + description: Specify cmake_build_type option to download specific artifact + required: true + + dependencies_artifact_postfix: + description: Specify artifact postfix in case it wants to use a manual one + required: false + default: _nightly + + target_workspace: + description: Specify directory to download dependencies + required: false + default: ${{ github.workspace }}/install + + # This must be passed as an argument because actions do not access to workflow secrets: + # Unrecognized named-value: 'secrets'. https://github.com/orgs/community/discussions/27054 + # Pass argument {{ secrets.GITHUB_TOKEN }} from workflow + secret_token: + description: 'Secret token to authenticate the WebRequest so it not get a rate limit error.' + required: false + default: '' + +runs: + using: composite + steps: + + - name: Install Fast DDS dependencies + uses: eProsima/eProsima-CI/multiplatform/install_fastdds_dependencies@feature/fix-cmake-version + with: + cmake_build_type: ${{ inputs.cmake_build_type }} + + - name: Install yaml cpp dependency + uses: eProsima/eProsima-CI/multiplatform/install_yamlcpp@v0 + with: + cmake_build_type: ${{ inputs.cmake_build_type }} + + # Fast DDS artifact + - name: Download dependencies artifact + uses: eProsima/eProsima-CI/multiplatform/download_dependency@v0 + with: + artifact_name: built_fastdds_${{ inputs.os }}_${{ inputs.cmake_build_type }}${{ inputs.dependencies_artifact_postfix }} + workflow_source: build_fastdds.yml + workflow_source_repository: eProsima/eProsima-CI + target_workspace: ${{ inputs.target_workspace }} + secret_token: ${{ inputs.secret_token }} + workflow_conclusion: completed diff --git a/.github/workflows/asan.meta b/.github/workflows/asan.meta deleted file mode 100644 index ae967a7e0..000000000 --- a/.github/workflows/asan.meta +++ /dev/null @@ -1,22 +0,0 @@ -{ - "names": - { - "fastrtps": - { - "cmake-args": - [ - "-DFASTDDS_STATISTICS=ON", - ] - }, - "fastdds_statistics_backend": - { - "cmake-args": - [ - "-DCMAKE_BUILD_TYPE=Debug", - "-DBUILD_TESTS=ON", - "-DASAN_BUILD=ON", - "-DCMAKE_CXX_FLAGS='-Werror'", - ] - } - } -} diff --git a/.github/workflows/asan_log_parser.py b/.github/workflows/asan_log_parser.py deleted file mode 100644 index 0b5f1b683..000000000 --- a/.github/workflows/asan_log_parser.py +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2022 Proyectos y Sistemas de Mantenimiento SL (eProsima). -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"Script to parse the colcon test output file." - -import re -import os - -from tomark import Tomark - -# Python summary file saved in GITHUB_STEP_SUMMARY environment variable -SUMMARY_FILE = os.getenv('GITHUB_STEP_SUMMARY') -LOG_FILE = 'log/latest_test/fastdds_statistics_backend/stdout_stderr.log' - -# Save the lines with failed tests -saved_lines = [] -with open(LOG_FILE, 'r') as file: - for line in reversed(file.readlines()): - saved_lines.append(line) - if (re.search('.*The following tests FAILED:.*', line)): - break - -# Exit if no test failed -if (not saved_lines): - exit(0) - -failed_tests = [] -for test in saved_lines: - match = re.search('\d* - .* \(Failed\)', test) - if (match): - split_test = match.group().split() - failed_tests.insert( - 0, - dict({'ID': split_test[0], 'Name': split_test[2]})) - -# Convert python dict to markdown table -if (failed_tests): - md_table = Tomark.table(failed_tests) - print(md_table) - # Save table of failed test to GitHub action summary file - with open(SUMMARY_FILE, 'a') as file: - file.write(f'\n{md_table}') -else: - print('NO TESTS FAILED!') - with open(SUMMARY_FILE, 'a') as file: - file.write('\nNO TESTS FAILED!') diff --git a/.github/workflows/ci.repos b/.github/workflows/ci.repos deleted file mode 100644 index 3f811e1ff..000000000 --- a/.github/workflows/ci.repos +++ /dev/null @@ -1,21 +0,0 @@ -repositories: - - foonathan_memory_vendor: - type: git - url: https://github.com/eProsima/foonathan_memory_vendor.git - version: master - - fastcdr: - type: git - url: https://github.com/eProsima/Fast-CDR.git - version: master - - fastdds: - type: git - url: https://github.com/eProsima/Fast-DDS.git - version: master - - googletest-distribution: - type: git - url: https://github.com/google/googletest.git - version: release-1.12.1 diff --git a/.github/workflows/clang.meta b/.github/workflows/clang.meta deleted file mode 100644 index 8d1ac84f4..000000000 --- a/.github/workflows/clang.meta +++ /dev/null @@ -1,20 +0,0 @@ -{ - "names": - { - "fastrtps": - { - "cmake-args": - [ - "-DFASTDDS_STATISTICS=ON", - ] - }, - "fastdds_statistics_backend": - { - "cmake-args": - [ - "-DCMAKE_BUILD_TYPE=Release", - "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON" - ] - } - } -} diff --git a/.github/workflows/configurations/Linux/colcon.meta b/.github/workflows/configurations/Linux/colcon.meta new file mode 100644 index 000000000..c3e503a78 --- /dev/null +++ b/.github/workflows/configurations/Linux/colcon.meta @@ -0,0 +1,5 @@ +{ + "names": + { + } +} diff --git a/.github/workflows/configurations/Windows/colcon.meta b/.github/workflows/configurations/Windows/colcon.meta new file mode 100644 index 000000000..a895a739c --- /dev/null +++ b/.github/workflows/configurations/Windows/colcon.meta @@ -0,0 +1,14 @@ +{ + "names": + { + "fastdds_statistics_backend": + { + "cmake-args": + [ + "-DCMAKE_CXX_FLAGS='/WX /EHsc'", + "-Ax64", + "-T host=x64" + ] + } + } +} diff --git a/.github/workflows/test.meta b/.github/workflows/test.meta deleted file mode 100644 index d5ed065d3..000000000 --- a/.github/workflows/test.meta +++ /dev/null @@ -1,24 +0,0 @@ -{ - "names": - { - "fastrtps": - { - "cmake-args": - [ - "-DFASTDDS_STATISTICS=ON", - ] - }, - "fastdds_statistics_backend": - { - "cmake-args": - [ - "-DBUILD_DOCUMENTATION=ON", - "-DBUILD_TESTS=ON", - "-DCMAKE_BUILD_TYPE=Debug", - "-DCOMPILE_EXAMPLES=ON", - "-DCMAKE_C_FLAGS='--coverage'", - "-DCMAKE_CXX_FLAGS='-Werror --coverage'" - ] - } - } -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 364759531..04b9dabdc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,475 +1,283 @@ -name: test +name: fastdds-statistics-backend-tests on: + workflow_dispatch: + inputs: + + dependencies_artifact_postfix: + description: 'Postfix name to add to artifact name to download dependencies. This is use to download a specific artifact version from eProsima-CI.' + required: true + default: '_nightly' + pull_request: push: branches: - main schedule: - - cron: '0 0 * * *' + - cron: '0 5 * * *' + +env: + code_packages_names: 'fastdds_statistics_backend' + docs_packages_names: 'fastdds_statistics_backend' + default_dependencies_artifact_postfix: '_nightly' jobs: - windows-build-test: - runs-on: windows-latest + + +##################################################################### +# TEST + + multiplatform-tests: + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - cmake-config: - - 'Release' - - 'Debug' - env: - CXXFLAGS: /MP - OPENSSL64_ROOT: "C:/Program Files/OpenSSL-Win64" + cmake_build_type: + - Release + - Debug + os: + - ubuntu-20.04 + - ubuntu-22.04 + - windows-2019 + - windows-2022 steps: - - name: Sync eProsima/Fast-DDS-Statistics-Backend repository - uses: actions/checkout@v2 + + - name: Sync repository + uses: eProsima/eProsima-CI/external/checkout@v0 with: - path: Fast-DDS-statistics-backend - - - name: Install OpenSSL - shell: pwsh - run: > - choco install openssl --version=3.1.1 -yr --no-progress; - @(ls -Path C:\Windows\System32\* -Include libssl-*.dll; ls -Path C:\Windows\SysWOW64\* -Include libssl-*.dll) - | rm -ErrorAction SilentlyContinue - - - name: Install GoogleTest - shell: pwsh - run: > - cmake --find-package -DNAME=GTest -DCOMPILER_ID=GNU -DLANGUAGE=CXX -DMODE=EXIST | Tee-Object -Variable res; - if ( $res -notlike '*GTest found.*') - { - git clone https://github.com/google/googletest.git; - cmake -DCMAKE_INSTALL_PREFIX='C:\Program Files\gtest' -Dgtest_force_shared_crt=ON -DBUILD_GMOCK=ON ` - -B build\gtest -A x64 -T host=x64 googletest; - cmake --build build\gtest --config ${{ matrix.cmake-config }} --target install; - } - - - name: Install foonatham memory - shell: pwsh - run: > - git clone --recurse-submodules --branch v0.6-2 https://github.com/foonathan/memory.git; - cmake -DBUILD_SHARED_LIBS=OFF -DFOONATHAN_MEMORY_BUILD_TOOLS=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON ` - -DFOONATHAN_MEMORY_BUILD_TESTS=OFF -Ax64 -T host=x64 -B build\memory memory; - cmake --build build\memory --config ${{ matrix.cmake-config }} --target install; - - - name: Install Fast-CDR - shell: pwsh - run: > - git clone https://github.com/eProsima/Fast-CDR.git --branch master; - cmake -Ax64 -T host=x64 -B build\fastcdr Fast-CDR; - cmake --build build\fastcdr --config ${{ matrix.cmake-config }} --target install; - - - name: Install Fast-DDS - shell: pwsh - run: > - git clone https://github.com/eProsima/Fast-DDS.git; - cmake -DTHIRDPARTY=ON -DSECURITY=ON -DCOMPILE_EXAMPLES=OFF -DEPROSIMA_BUILD_TESTS=OFF ` - -DINTERNAL_DEBUG=ON -Ax64 -T host=x64 -B build\fastdds Fast-DDS; - cmake --build build\fastdds --config ${{ matrix.cmake-config }} --target install; - - - name: Install Fast-DDS-statistics-backend - shell: pwsh - run: > - cmake -DCMAKE_PREFIX_PATH='C:\Program Files\gtest' -DBUILD_LIBRARY_TESTS=ON ` - -B build\backend -A x64 -T host=x64 Fast-DDS-statistics-backend; - cmake --build build\backend --config ${{ matrix.cmake-config }} --target install; - - - name: Run tests on ${{ matrix.cmake-config }} - shell: pwsh - run: ctest -C ${{ matrix.cmake-config }} --test-dir build\backend -V --timeout 60 --label-exclude xfail - - - ubuntu-build-test: - runs-on: ubuntu-latest - environment: - name: codecov + path: ${{ github.workspace }}/src - steps: - - uses: actions/checkout@v2 + - name: Download dependencies and install requirements + uses: ./src/.github/actions/project_dependencies + with: + os: ${{ matrix.os }} + cmake_build_type: ${{ matrix.cmake_build_type }} + dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} + secret_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compile and run tests + id: compile_and_test + uses: eProsima/eProsima-CI/multiplatform/colcon_build_test@v0 + with: + packages_names: ${{ env.code_packages_names }} + cmake_args: -DBUILD_TESTS=ON -DCOMPILE_EXAMPLES=ON -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} + workspace_dependencies: ${{ github.workspace }}/install + ctest_args: --label-exclude "xfail" + colcon_meta_file: ${{ github.workspace }}/src/.github/workflows/configurations/${{ runner.os }}/colcon.meta + + - name: Test Report + uses: eProsima/eProsima-CI/external/test-reporter@v0 + if: success() || failure() with: - path: src/Fast-DDS-statistics-backend + name: "Report: ${{ matrix.os }} | ${{ matrix.cmake_build_type }} " + path: "${{ steps.compile_and_test.outputs.ctest_results_path }}*.xml" + working-directory: 'src' + path-replace-backslashes: 'true' + list-tests: 'failed' - - name: Install apt packages - uses: ./src/Fast-DDS-statistics-backend/.github/actions/install-apt-packages +##################################################################### +# ASAN - - name: Install colcon - uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + asan: + runs-on: ubuntu-22.04 + steps: - - name: Install Python packages - uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 - with: - packages: \ - sphinx==3.0.3 \ - breathe==4.19.0 \ - doc8==0.8.0 \ - sphinx_rtd_theme==0.4.3 \ - sphinxcontrib.spelling==5.0.0 \ - sphinxcontrib-imagehelper==1.1.1 \ - vcstool \ - GitPython \ - setuptools \ - gcovr==5.2 \ - tomark - - - name: Fetch eProsima dependencies - run: | - vcs import src < ./src/Fast-DDS-statistics-backend/.github/workflows/ci.repos - - - name: Update colcon mixin - run: | - colcon mixin add default \ - https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml - colcon mixin update default - continue-on-error: true - - - name: Build gtest - run: | - colcon build \ - --event-handlers=console_direct+ \ - --packages-select googletest-distribution - - - name: Build workspace - run: | - cat src/Fast-DDS-statistics-backend/.github/workflows/test.meta - colcon build \ - --packages-skip googletest-distribution \ - --event-handlers=console_direct+ \ - --metas src/Fast-DDS-statistics-backend/.github/workflows/test.meta - - - name: Run tests - run: | - source install/setup.bash && \ - colcon test \ - --packages-select fastdds_statistics_backend \ - --event-handlers=console_direct+ \ - --return-code-on-test-failure \ - --ctest-args \ - --timeout 60 \ - --label-exclude xfail - - - name: Generate coverage report - run: | - cp src/Fast-DDS-statistics-backend/codecov.yml . - mkdir coverage-report - gcovr \ - --root src/Fast-DDS-statistics-backend/ \ - --object-directory build/fastdds_statistics_backend \ - --output coverage-report/coverage.xml \ - --xml-pretty \ - --exclude='.*nlohmann-json/.*' \ - --exclude='.*docs/.*' \ - --exclude='.*examples/.*' \ - --exclude='.*test/.*' \ - --exclude='.*github/.*' \ - --exclude='.*topic_types/.*' \ - --exclude-unreachable-branches - if: always() - - - name: Upload coverage - uses: actions/upload-artifact@v1 + - name: Sync repository + uses: eProsima/eProsima-CI/external/checkout@v0 with: - name: coverage-report - path: coverage-report/ - if: always() + path: ${{ github.workspace }}/src - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v1 + - name: Download dependencies and install requirements + uses: ./src/.github/actions/project_dependencies with: - token: ${{ secrets.CODECOV_TOKEN }} - file: coverage-report/coverage.xml - root_dir: src/Fast-DDS-statistics-backend - fail_ci_if_error: true + os: ubuntu-22.04 + cmake_build_type: Release + dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} + secret_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compile and run tests + id: compile_and_test + uses: eProsima/eProsima-CI/multiplatform/asan_build_test@v0 + with: + packages_names: ${{ env.code_packages_names }} + workspace_dependencies: ${{ github.workspace }}/install - - name: Upload Logs - uses: actions/upload-artifact@v1 + - name: Test Report + uses: eProsima/eProsima-CI/external/test-reporter@v0 + if: success() || failure() with: - name: colcon-logs-ubuntu - path: log/ - if: always() + name: "Report: ASAN " + path: "${{ steps.compile_and_test.outputs.ctest_results_path }}*.xml" + working-directory: 'src' + list-tests: 'failed' + +##################################################################### +# TSAN + +# Commented out until we have a TSAN build for Fast DDS Statistics Backend + +# tsan: +# runs-on: ubuntu-22.04 +# steps: + +# - name: Sync repository +# uses: eProsima/eProsima-CI/external/checkout@v0 +# with: +# path: ${{ github.workspace }}/src + +# - name: Download dependencies and install requirements +# uses: ./src/.github/actions/project_dependencies +# with: +# os: ubuntu-22.04 +# cmake_build_type: Release +# dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} +# secret_token: ${{ secrets.GITHUB_TOKEN }} + +# - name: Compile and run tests +# id: compile_and_test +# uses: eProsima/eProsima-CI/multiplatform/tsan_build_test@v0 +# with: +# packages_names: ${{ env.code_packages_names }} +# workspace_dependencies: ${{ github.workspace }}/install + +# - name: Test Report +# uses: eProsima/eProsima-CI/external/test-reporter@v0 +# if: success() || failure() +# with: +# name: "Report: TSAN " +# path: "${{ steps.compile_and_test.outputs.ctest_results_path }}*.xml" +# working-directory: 'src' +# list-tests: 'failed' + +##################################################################### +# CLANG + + clang: + runs-on: ubuntu-22.04 + steps: - - name: Upload documentation - uses: actions/upload-artifact@v1 + - name: Sync repository + uses: eProsima/eProsima-CI/external/checkout@v0 with: - name: Documentation HTML - path: install/fastdds_statistics_backend/docs/fastdds_statistics_backend/sphinx/html/ - if: always() + path: ${{ github.workspace }}/src - asan-test: - runs-on: ubuntu-latest + - name: Download dependencies and install requirements + uses: ./src/.github/actions/project_dependencies + with: + os: ubuntu-22.04 + cmake_build_type: Release + dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} + secret_token: ${{ secrets.GITHUB_TOKEN }} - steps: - - uses: actions/checkout@v2 + - name: Compile and run tests + uses: eProsima/eProsima-CI/multiplatform/clang_build_test@v0 with: - path: src/Fast-DDS-statistics-backend + packages_names: ${{ env.code_packages_names }} + workspace_dependencies: ${{ github.workspace }}/install - - name: Install apt packages - uses: ./src/Fast-DDS-statistics-backend/.github/actions/install-apt-packages +##################################################################### +# COVERAGE - - name: Install colcon - uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + coverage: + runs-on: ubuntu-22.04 + environment: + name: codecov + steps: - - name: Install Python packages - uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 + - name: Sync repository + uses: eProsima/eProsima-CI/external/checkout@v0 with: - packages: \ - sphinx==3.0.3 \ - breathe==4.19.0 \ - doc8==0.8.0 \ - sphinx_rtd_theme==0.4.3 \ - sphinxcontrib.spelling==5.0.0 \ - sphinxcontrib-imagehelper==1.1.1 \ - vcstool \ - GitPython \ - setuptools \ - gcovr==5.2 \ - tomark - - - name: Fetch eProsima dependencies - run: | - vcs import src < ./src/Fast-DDS-statistics-backend/.github/workflows/ci.repos - - - name: Update colcon mixin - run: | - colcon mixin add default \ - https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml - colcon mixin update default - continue-on-error: true - - - name: Build workspace - run: | - cat src/Fast-DDS-statistics-backend/.github/workflows/asan.meta - colcon build \ - --event-handlers=console_direct+ \ - --metas src/Fast-DDS-statistics-backend/.github/workflows/asan.meta - - - name: Run tests - run: | - source install/setup.bash && \ - colcon test \ - --packages-select fastdds_statistics_backend \ - --event-handlers=console_direct+ \ - --return-code-on-test-failure \ - --ctest-args \ - --timeout 60 \ - --label-exclude xasan xfail - - - name: Upload Logs - uses: actions/upload-artifact@v1 + path: ${{ github.workspace }}/src + + - name: Download dependencies and install requirements + uses: ./src/.github/actions/project_dependencies with: - name: asan-logs - path: log/ - if: always() + os: ubuntu-22.04 + cmake_build_type: Release + dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} + secret_token: ${{ secrets.GITHUB_TOKEN }} - - name: Report ASAN errors - continue-on-error: true - if: always() - run: | - echo -n "**ASAN Errors**: " >> $GITHUB_STEP_SUMMARY - echo $(sed 's/==.*==ERROR:/==.*==ERROR:\n/g' log/latest_test/fastdds_statistics_backend/stdout_stderr.log | grep -c "==.*==ERROR:") >> $GITHUB_STEP_SUMMARY - python3 src/Fast-DDS-statistics-backend/.github/workflows/asan_log_parser.py + - name: Compile and run tests + uses: eProsima/eProsima-CI/ubuntu/coverage_build_test_upload@v0 + with: + packages_names: ${{ env.code_packages_names }} + workspace_dependencies: ${{ github.workspace }}/install + codecov_token: ${{ secrets.CODECOV_TOKEN }} + codecov_fix_file_path: ${{ github.workspace }}/src/codecov.yml - flaky-test: - runs-on: ubuntu-latest +##################################################################### +# FLAKY + flaky: + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - with: - path: src/Fast-DDS-statistics-backend - - - name: Install apt packages - uses: ./src/Fast-DDS-statistics-backend/.github/actions/install-apt-packages - - name: Install colcon - uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + - name: Sync repository + uses: eProsima/eProsima-CI/external/checkout@v0 + with: + path: ${{ github.workspace }}/src - - name: Install Python packages - uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 + - name: Download dependencies and install requirements + uses: ./src/.github/actions/project_dependencies with: - packages: \ - sphinx==3.0.3 \ - breathe==4.19.0 \ - doc8==0.8.0 \ - sphinx_rtd_theme==0.4.3 \ - sphinxcontrib.spelling==5.0.0 \ - sphinxcontrib-imagehelper==1.1.1 \ - vcstool \ - GitPython \ - setuptools \ - gcovr==5.2 \ - tomark - - - name: Fetch eProsima dependencies - run: | - vcs import src < ./src/Fast-DDS-statistics-backend/.github/workflows/ci.repos - - - name: Update colcon mixin - run: | - colcon mixin add default \ - https://raw.githubusercontent.com/colcon/colcon-mixin-repository/master/index.yaml - colcon mixin update default - continue-on-error: true - - - name: Build workspace - run: | - cat src/Fast-DDS-statistics-backend/.github/workflows/test.meta - colcon build \ - --event-handlers=console_direct+ \ - --metas src/Fast-DDS-statistics-backend/.github/workflows/test.meta - - - name: Run tests - run: | - source install/setup.bash && \ - colcon test \ - --packages-select fastdds_statistics_backend \ - --event-handlers=console_direct+ \ - --return-code-on-test-failure \ - --ctest-args \ - --timeout 60 \ - --label-regex xfail - - - name: Upload Logs - uses: actions/upload-artifact@v1 + os: ubuntu-22.04 + cmake_build_type: Release + dependencies_artifact_postfix: ${{ github.event.inputs.dependencies_artifact_postfix || env.default_dependencies_artifact_postfix }} + secret_token: ${{ secrets.GITHUB_TOKEN }} + + - name: Compile and run tests + id: compile_and_test + uses: eProsima/eProsima-CI/multiplatform/colcon_build_test@v0 with: - name: asan-logs - path: log/ - if: always() + packages_names: ${{ env.code_packages_names }} + workspace_dependencies: ${{ github.workspace }}/install + ctest_args: --label-regex "xfail" - - clang-tidy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + - name: Test Report + uses: eProsima/eProsima-CI/external/test-reporter@v0 + if: success() || failure() with: - path: src/Fast-DDS-statistics-backend + name: "Report: Flaky " + path: "${{ steps.compile_and_test.outputs.ctest_results_path }}*.xml" + working-directory: 'src' + fail-on-empty: 'false' + list-tests: 'failed' - - name: Install apt packages - uses: ./src/Fast-DDS-statistics-backend/.github/actions/install-apt-packages +##################################################################### +# DOCUMENTATION TEST - - name: Install colcon - uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 + docs: + runs-on: ubuntu-22.04 + steps: - - name: Install Python packages - uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 + - name: Install libtinyxml in ubuntu + uses: eProsima/eProsima-CI/ubuntu/install_apt_packages@v0 with: - packages: \ - sphinx==3.0.3 \ - breathe==4.19.0 \ - doc8==0.8.0 \ - sphinx_rtd_theme==0.4.3 \ - sphinxcontrib.spelling==5.0.0 \ - sphinxcontrib-imagehelper==1.1.1 \ - vcstool \ - GitPython \ - setuptools \ - gcovr==5.2 \ - tomark - - - name: Fetch eProsima dependencies - run: | - vcs import src < ./src/Fast-DDS-statistics-backend/.github/workflows/ci.repos - - - name: Build workspace - run: | - cat src/Fast-DDS-statistics-backend/.github/workflows/clang.meta - colcon build \ - --event-handlers=console_direct+ \ - --metas src/Fast-DDS-statistics-backend/.github/workflows/clang.meta - - - name: Run clang-tidy - run: | - cd build/fastdds_statistics_backend - run-clang-tidy -header-filter='.*' -checks='clang-analyzer-cplusplus' -quiet 2> ${{ github.workspace }}/clang_results.yml + packages: libtinyxml2-dev - uncrustify: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 + - name: Install doxygen + uses: eProsima/eProsima-CI/ubuntu/install_apt_packages@v0 with: - path: src/Fast-DDS-statistics-backend + packages: doxygen - - name: Fetch all branches and tags - run: | - cd src/Fast-DDS-statistics-backend - git fetch --prune --unshallow + - name: Build and test documentation + uses: eProsima/eProsima-CI/ubuntu/sphinx_docs@v0 + with: + checkout_path: "${{ github.workspace }}/src/fastdds_statistics_backend" + path_to_requirements: "${{ github.workspace }}/src/fastdds_statistics_backend/docs/requirements.txt" + docs_subpackage_name: ${{ env.docs_packages_names }} + secret_token: ${{ secrets.GITHUB_TOKEN }} - - name: Install apt packages - uses: ./src/Fast-DDS-statistics-backend/.github/actions/install-apt-packages - - name: Install colcon - uses: eProsima/eProsima-CI/ubuntu/install_colcon@v0 +##################################################################### +# UNCRUSTIFY - - name: Install Python packages - uses: eProsima/eProsima-CI/ubuntu/install_python_packages@v0 - with: - packages: \ - sphinx==3.0.3 \ - breathe==4.19.0 \ - doc8==0.8.0 \ - sphinx_rtd_theme==0.4.3 \ - sphinxcontrib.spelling==5.0.0 \ - sphinxcontrib-imagehelper==1.1.1 \ - vcstool \ - GitPython \ - setuptools \ - gcovr==5.2 \ - tomark - - - name: Install uncrustify - run: | - git clone https://github.com/uncrustify/uncrustify.git \ - --branch uncrustify-0.71.0 \ - --single-branch uncrustify - mkdir -p uncrustify/build - cd uncrustify/build - cmake .. - sudo cmake --build . --target install - cd ../.. - sudo rm -rf uncrustify - - - name: Clone ament_lint - run: | - git clone https://github.com/ament/ament_lint.git src/ament_lint - - - name: Build ament_uncrustify - run: colcon build --packages-up-to ament_uncrustify - - - name: Fetch uncrustify config file - run: | - curl \ - -l https://raw.githubusercontent.com/eProsima/cpp-style/master/uncrustify.cfg \ - -o uncrustify.cfg - - - name: Get difference - run: | - cd src/Fast-DDS-statistics-backend - echo "MODIFIED_FILES=$(git diff --name-only origin/${GITHUB_BASE_REF} origin/${GITHUB_HEAD_REF} | grep -e '\.h' -e '\.hpp' -e '\.cpp' | tr '\n' ' ')" >> $GITHUB_ENV - - - name: Check difference - run: | - cd src/Fast-DDS-statistics-backend - if [[ -z "${MODIFIED_FILES}" ]] - then - touch empty.hpp - echo "MODIFIED_FILES=empty.hpp" >> $GITHUB_ENV - fi - - - name: Check style - run: | - source install/local_setup.bash - cd src/Fast-DDS-statistics-backend - ament_uncrustify \ - -c ../../uncrustify.cfg \ - --language CPP \ - --xunit-file ../../uncrustify_results.xml \ - ${MODIFIED_FILES} - - - name: Upload uncrustify results - uses: actions/upload-artifact@v1 - with: - name: uncrustify_results - path: uncrustify_results.xml - if: always() + uncrustify: + runs-on: ubuntu-22.04 + steps: + + - name: Uncrustify + uses: eProsima/eProsima-CI/ubuntu/uncrustify@bugfix/fix_uncrustify diff --git a/CMakeLists.txt b/CMakeLists.txt index b0d85bf85..df4c59fc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,32 +121,26 @@ endif() ############################################################################### # Test ############################################################################### -option(BUILD_TESTS "Build Fast DDS Statistics Backend library and documentation tests" OFF) -option(BUILD_LIBRARY_TESTS "Build Fast DDS Statistics Backend library tests" OFF) -option(BUILD_DOCUMENTATION_TESTS "Build Fast DDS Statistics Backend documentation tests" OFF) +option(BUILD_TESTS "Build Fast DDS Statistics Backend library tests" OFF) +option(BUILD_DOCS_TESTS "Build Fast DDS Statistics Backend documentation tests" OFF) -if (BUILD_TESTS) - set(BUILD_LIBRARY_TESTS ON) - set(BUILD_DOCUMENTATION_TESTS ON) -endif() - -if(BUILD_LIBRARY_TESTS OR BUILD_DOCUMENTATION_TESTS) +if (BUILD_TESTS OR BUILD_DOCS_TESTS) # CTest needs to be included here, otherwise it is not possible to run the tests from the root # of the build directory enable_testing() include(CTest) endif() -if (BUILD_LIBRARY_TESTS) +if (BUILD_TESTS) add_subdirectory(test) endif() ############################################################################### # Documentation ############################################################################### -option(BUILD_DOCUMENTATION "Generate documentation" OFF) -if(BUILD_DOCUMENTATION OR BUILD_DOCUMENTATION_TESTS) - set(BUILD_DOCUMENTATION ON) +option(BUILD_DOCS "Generate documentation" OFF) +if(BUILD_DOCS OR BUILD_DOCS_TESTS) + set(BUILD_DOCS ON) add_subdirectory(docs) endif() diff --git a/README.md b/README.md index 12f30bae5..ad5b3db8f 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ python3 -V #### Build documentation To enable the documentation building, edit `/fastdds_statistics_backend_ws/colcon.meta` -to set `-DBUILD_DOCUMENTATION` to `ON` within the `fastdds_statistics_backend` project. +to set `-DBUILD_DOCS` to `ON` within the `fastdds_statistics_backend` project. Then, activate the virtual environment and build the documentation. ```bash diff --git a/cmake/modules/FindGMock.cmake b/cmake/modules/FindGMock.cmake index 2ad922129..6ca589051 100644 --- a/cmake/modules/FindGMock.cmake +++ b/cmake/modules/FindGMock.cmake @@ -97,12 +97,12 @@ if(MSVC) endif() endif() -find_path(GMOCK_INCLUDE_DIR gmock/gmock.h +find_path(GTEST_INCLUDE_DIR gmock/gmock.h HINTS $ENV{GMOCK_ROOT}/include ${GMOCK_ROOT}/include ) -mark_as_advanced(GMOCK_INCLUDE_DIR) +mark_as_advanced(GTEST_INCLUDE_DIR) if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") # The provided /MD project files for Google Mock add -md suffixes to the @@ -119,12 +119,11 @@ else() endif() include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARY GTEST_INCLUDE_DIR GMOCK_MAIN_LIBRARY) if(GMOCK_FOUND) - set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) + set(GMOCK_INCLUDE_DIRS ${GTEST_INCLUDE_DIR}) _gmock_append_debugs(GMOCK_LIBRARIES GMOCK_LIBRARY) _gmock_append_debugs(GMOCK_MAIN_LIBRARIES GMOCK_MAIN_LIBRARY) set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES}) endif() - diff --git a/colcon.meta b/colcon.meta index f21e94f61..46982a737 100644 --- a/colcon.meta +++ b/colcon.meta @@ -12,7 +12,7 @@ { "cmake-args": [ - "-DBUILD_DOCUMENTATION=OFF", + "-DBUILD_DOCS=OFF", "-DCOMPILE_EXAMPLES=OFF", ] } diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 9e6b65033..100ddddb1 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -100,7 +100,7 @@ set(CPACK_COMPONENT_backend-sphinx_DESCRIPTION set(CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} ${DOCS_BUILDER}) # Add tests if required -if(BUILD_DOCUMENTATION_TESTS) +if(BUILD_DOCS_TESTS) message(STATUS "Adding documentation tests") add_subdirectory(test) endif() diff --git a/docs/requirements.txt b/docs/requirements.txt index 945c11c27..734953be4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -8,7 +8,12 @@ setuptools==67.6.0 sphinx_rtd_theme==0.5.2 sphinx-tabs==3.2.0 sphinx==4.3.1 +sphinxcontrib-applehelp==1.0.4 +sphinxcontrib-devhelp==1.0.2 +sphinxcontrib-htmlhelp==2.0.1 sphinxcontrib-imagehelper==1.1.1 sphinxcontrib-plantuml==0.22 +sphinxcontrib-qthelp==1.0.3 +sphinxcontrib-serializinghtml==1.1.5 sphinxcontrib.spelling==7.2.1 vcstool==0.3.0 diff --git a/docs/rst/installation/cmake_options.rst b/docs/rst/installation/cmake_options.rst index 7f0b353e0..b5b2266e5 100644 --- a/docs/rst/installation/cmake_options.rst +++ b/docs/rst/installation/cmake_options.rst @@ -14,24 +14,18 @@ CMake options - Description - Possible values - Default - * - :class:`BUILD_DOCUMENTATION` + * - :class:`BUILD_DOCS` - Build the library documentation. Set to ``ON`` if |br| - :class:`BUILD_DOCUMENTATION_TESTS` is set to ``ON``. + :class:`BUILD_DOCS_TESTS` is set to ``ON``. - ``ON`` ``OFF`` - ``OFF`` - * - :class:`BUILD_LIBRARY_TESTS` + * - :class:`BUILD_TESTS` - Build the library tests. - ``ON`` ``OFF`` - ``OFF`` - * - :class:`BUILD_DOCUMENTATION_TESTS` + * - :class:`BUILD_DOCS_TESTS` - Build the library documentation tests. Setting this |br| - ``ON`` will set :class:`BUILD_DOCUMENTATION` to ``ON`` - - ``ON`` ``OFF`` - - ``OFF`` - * - :class:`BUILD_TESTS` - - Build the library and documentation tests. Setting this |br| - ``ON`` will set :class:`BUILD_LIBRARY_TESTS` and |br| - :class:`BUILD_DOCUMENTATION_TESTS` to ``ON`` + ``ON`` will set :class:`BUILD_DOCS` to ``ON`` - ``ON`` ``OFF`` - ``OFF`` * - :class:`BUILD_SHARED_LIBS` diff --git a/examples/cpp/HelloWorldExample/Monitor.cpp b/examples/cpp/HelloWorldExample/Monitor.cpp index c295e979a..74bf65f58 100644 --- a/examples/cpp/HelloWorldExample/Monitor.cpp +++ b/examples/cpp/HelloWorldExample/Monitor.cpp @@ -63,7 +63,7 @@ void Monitor::stop() bool Monitor::init( uint32_t domain, - uint32_t n_bins, + uint16_t n_bins, uint32_t t_interval, std::string dump_file /* = "" */, bool reset /* = false */) @@ -124,7 +124,12 @@ void Monitor::dump_in_file() { // Get current timestamp auto t = std::time(nullptr); - auto tm = *std::localtime(&t); + std::tm tm; +#ifdef _WIN32 + localtime_s(&tm, &t); +#else + localtime_r(&t, &tm); +#endif // ifdef _WIN32 std::ostringstream oss; oss << std::put_time(&tm, "%Y-%m-%d_%H-%M-%S"); std::string current_time = oss.str(); diff --git a/examples/cpp/HelloWorldExample/Monitor.h b/examples/cpp/HelloWorldExample/Monitor.h index 1f8ada609..3c0e53226 100644 --- a/examples/cpp/HelloWorldExample/Monitor.h +++ b/examples/cpp/HelloWorldExample/Monitor.h @@ -45,7 +45,7 @@ class Monitor //! Initialize monitor bool init( uint32_t domain, - uint32_t n_bins, + uint16_t n_bins, uint32_t t_interval, std::string dump_file = "", bool reset = false); @@ -143,7 +143,7 @@ class Monitor private: //! Number of time intervals in which the measurement time is divided - uint32_t n_bins_; + uint16_t n_bins_; //! Time interval of the returned measures uint32_t t_interval_; diff --git a/examples/cpp/HelloWorldExample/main.cpp b/examples/cpp/HelloWorldExample/main.cpp index fe3f1b851..6d7f29aa8 100644 --- a/examples/cpp/HelloWorldExample/main.cpp +++ b/examples/cpp/HelloWorldExample/main.cpp @@ -242,7 +242,7 @@ int main( Monitor monitor; if (monitor.init( static_cast(domain), - static_cast(n_bins), + static_cast(n_bins), static_cast(t_interval), dump_file, reset)) diff --git a/examples/cpp/HelloWorldExample/optionparser.h b/examples/cpp/HelloWorldExample/optionparser.h index 16fda026f..5d1975f30 100644 --- a/examples/cpp/HelloWorldExample/optionparser.h +++ b/examples/cpp/HelloWorldExample/optionparser.h @@ -217,23 +217,24 @@ #define OPTIONPARSER_H_ /** @brief The namespace of The Lean Mean C++ Option Parser. */ -namespace option -{ +namespace option { #ifdef _MSC_VER #include #pragma intrinsic(_BitScanReverse) struct MSC_Builtin_CLZ { - static int builtin_clz(unsigned x) - { - unsigned long index; - _BitScanReverse(&index, x); - return 32-index; // int is always 32bit on Windows, even for target x64 - } + static int builtin_clz( + unsigned x) + { + unsigned long index; + _BitScanReverse(&index, x); + return 32 - index; // int is always 32bit on Windows, even for target x64 + } + }; #define __builtin_clz(x) MSC_Builtin_CLZ::builtin_clz(x) -#endif +#endif // ifdef _MSC_VER class Option; @@ -245,14 +246,14 @@ class Option; */ enum ArgStatus { - //! The option does not take an argument. - ARG_NONE, - //! The argument is acceptable for the option. - ARG_OK, - //! The argument is not acceptable but that's non-fatal because the option's argument is optional. - ARG_IGNORE, - //! The argument is not acceptable and that's fatal. - ARG_ILLEGAL + //! The option does not take an argument. + ARG_NONE, + //! The argument is acceptable for the option. + ARG_OK, + //! The argument is not acceptable but that's non-fatal because the option's argument is optional. + ARG_IGNORE, + //! The argument is not acceptable and that's fatal. + ARG_ILLEGAL }; /** @@ -283,7 +284,9 @@ enum ArgStatus * @li @c Arg::Optional @copybrief Arg::Optional * */ -typedef ArgStatus (*CheckArg)(const Option& option, bool msg); +typedef ArgStatus (* CheckArg)( + const Option& option, + bool msg); /** * @brief Describes an option, its help text (usage) and how it should be parsed. @@ -309,112 +312,112 @@ typedef ArgStatus (*CheckArg)(const Option& option, bool msg); */ struct Descriptor { - /** - * @brief Index of this option's linked list in the array filled in by the parser. - * - * Command line options whose Descriptors have the same index will end up in the same - * linked list in the order in which they appear on the command line. If you have - * multiple long option aliases that refer to the same option, give their descriptors - * the same @c index. - * - * If you have options that mean exactly opposite things - * (e.g. @c --enable-foo and @c --disable-foo ), you should also give them the same - * @c index, but distinguish them through different values for @ref type. - * That way they end up in the same list and you can just take the last element of the - * list and use its type. This way you get the usual behaviour where switches later - * on the command line override earlier ones without having to code it manually. - * - * @par Tip: - * Use an enum rather than plain ints for better readability, as shown in the example - * at Descriptor. - */ - const unsigned index; - - /** - * @brief Used to distinguish between options with the same @ref index. - * See @ref index for details. - * - * It is recommended that you use an enum rather than a plain int to make your - * code more readable. - */ - const int type; - - /** - * @brief Each char in this string will be accepted as a short option character. - * - * The string must not include the minus character @c '-' or you'll get undefined - * behaviour. - * - * If this Descriptor should not have short option characters, use the empty - * string "". NULL is not permitted here! - * - * See @ref longopt for more information. - */ - const char* const shortopt; - - /** - * @brief The long option name (without the leading @c -- ). - * - * If this Descriptor should not have a long option name, use the empty - * string "". NULL is not permitted here! - * - * While @ref shortopt allows multiple short option characters, each - * Descriptor can have only a single long option name. If you have multiple - * long option names referring to the same option use separate Descriptors - * that have the same @ref index and @ref type. You may repeat - * short option characters in such an alias Descriptor but there's no need to. - * - * @par Dummy Descriptors: - * You can use dummy Descriptors with an - * empty string for both @ref shortopt and @ref longopt to add text to - * the usage that is not related to a specific option. See @ref help. - * The first dummy Descriptor will be used for unknown options (see below). - * - * @par Unknown Option Descriptor: - * The first dummy Descriptor in the list of Descriptors, - * whose @ref shortopt and @ref longopt are both the empty string, will be used - * as the Descriptor for unknown options. An unknown option is a string in - * the argument vector that is not a lone minus @c '-' but starts with a minus - * character and does not match any Descriptor's @ref shortopt or @ref longopt. @n - * Note that the dummy descriptor's @ref check_arg function @e will be called and - * its return value will be evaluated as usual. I.e. if it returns @ref ARG_ILLEGAL - * the parsing will be aborted with Parser::error()==true. @n - * if @c check_arg does not return @ref ARG_ILLEGAL the descriptor's - * @ref index @e will be used to pick the linked list into which - * to put the unknown option. @n - * If there is no dummy descriptor, unknown options will be dropped silently. - * - */ - const char* const longopt; - - /** - * @brief For each option that matches @ref shortopt or @ref longopt this function - * will be called to check a potential argument to the option. - * - * This function will be called even if there is no potential argument. In that case - * it will be passed @c NULL as @c arg parameter. Do not confuse this with the empty - * string. - * - * See @ref CheckArg for more information. - */ - const CheckArg check_arg; - - /** - * @brief The usage text associated with the options in this Descriptor. - * - * You can use option::printUsage() to format your usage message based on - * the @c help texts. You can use dummy Descriptors where - * @ref shortopt and @ref longopt are both the empty string to add text to - * the usage that is not related to a specific option. - * - * See option::printUsage() for special formatting characters you can use in - * @c help to get a column layout. - * - * @attention - * Must be UTF-8-encoded. If your compiler supports C++11 you can use the "u8" - * prefix to make sure string literals are properly encoded. - */ - const char* help; + /** + * @brief Index of this option's linked list in the array filled in by the parser. + * + * Command line options whose Descriptors have the same index will end up in the same + * linked list in the order in which they appear on the command line. If you have + * multiple long option aliases that refer to the same option, give their descriptors + * the same @c index. + * + * If you have options that mean exactly opposite things + * (e.g. @c --enable-foo and @c --disable-foo ), you should also give them the same + * @c index, but distinguish them through different values for @ref type. + * That way they end up in the same list and you can just take the last element of the + * list and use its type. This way you get the usual behaviour where switches later + * on the command line override earlier ones without having to code it manually. + * + * @par Tip: + * Use an enum rather than plain ints for better readability, as shown in the example + * at Descriptor. + */ + const unsigned index; + + /** + * @brief Used to distinguish between options with the same @ref index. + * See @ref index for details. + * + * It is recommended that you use an enum rather than a plain int to make your + * code more readable. + */ + const int type; + + /** + * @brief Each char in this string will be accepted as a short option character. + * + * The string must not include the minus character @c '-' or you'll get undefined + * behaviour. + * + * If this Descriptor should not have short option characters, use the empty + * string "". NULL is not permitted here! + * + * See @ref longopt for more information. + */ + const char* const shortopt; + + /** + * @brief The long option name (without the leading @c -- ). + * + * If this Descriptor should not have a long option name, use the empty + * string "". NULL is not permitted here! + * + * While @ref shortopt allows multiple short option characters, each + * Descriptor can have only a single long option name. If you have multiple + * long option names referring to the same option use separate Descriptors + * that have the same @ref index and @ref type. You may repeat + * short option characters in such an alias Descriptor but there's no need to. + * + * @par Dummy Descriptors: + * You can use dummy Descriptors with an + * empty string for both @ref shortopt and @ref longopt to add text to + * the usage that is not related to a specific option. See @ref help. + * The first dummy Descriptor will be used for unknown options (see below). + * + * @par Unknown Option Descriptor: + * The first dummy Descriptor in the list of Descriptors, + * whose @ref shortopt and @ref longopt are both the empty string, will be used + * as the Descriptor for unknown options. An unknown option is a string in + * the argument vector that is not a lone minus @c '-' but starts with a minus + * character and does not match any Descriptor's @ref shortopt or @ref longopt. @n + * Note that the dummy descriptor's @ref check_arg function @e will be called and + * its return value will be evaluated as usual. I.e. if it returns @ref ARG_ILLEGAL + * the parsing will be aborted with Parser::error()==true. @n + * if @c check_arg does not return @ref ARG_ILLEGAL the descriptor's + * @ref index @e will be used to pick the linked list into which + * to put the unknown option. @n + * If there is no dummy descriptor, unknown options will be dropped silently. + * + */ + const char* const longopt; + + /** + * @brief For each option that matches @ref shortopt or @ref longopt this function + * will be called to check a potential argument to the option. + * + * This function will be called even if there is no potential argument. In that case + * it will be passed @c NULL as @c arg parameter. Do not confuse this with the empty + * string. + * + * See @ref CheckArg for more information. + */ + const CheckArg check_arg; + + /** + * @brief The usage text associated with the options in this Descriptor. + * + * You can use option::printUsage() to format your usage message based on + * the @c help texts. You can use dummy Descriptors where + * @ref shortopt and @ref longopt are both the empty string to add text to + * the usage that is not related to a specific option. + * + * See option::printUsage() for special formatting characters you can use in + * @c help to get a column layout. + * + * @attention + * Must be UTF-8-encoded. If your compiler supports C++11 you can use the "u8" + * prefix to make sure string literals are properly encoded. + */ + const char* help; }; /** @@ -436,397 +439,424 @@ struct Descriptor */ class Option { - Option* next_; - Option* prev_; + Option* next_; + Option* prev_; + public: - /** - * @brief Pointer to this Option's Descriptor. - * - * Remember that the first dummy descriptor (see @ref Descriptor::longopt) is used - * for unknown options. - * - * @attention - * @c desc==NULL signals that this Option is unused. This is the default state of - * elements in the result array. You don't need to test @c desc explicitly. You - * can simply write something like this: - * @code - * if (options[CREATE]) - * { - * ... - * } - * @endcode - * This works because of operator const Option*() . - */ - const Descriptor* desc; - - /** - * @brief The name of the option as used on the command line. - * - * The main purpose of this string is to be presented to the user in messages. - * - * In the case of a long option, this is the actual @c argv pointer, i.e. the first - * character is a '-'. In the case of a short option this points to the option - * character within the @c argv string. - * - * Note that in the case of a short option group or an attached option argument, this - * string will contain additional characters following the actual name. Use @ref namelen - * to filter out the actual option name only. - * - */ - const char* name; - - /** - * @brief Pointer to this Option's argument (if any). - * - * NULL if this option has no argument. Do not confuse this with the empty string which - * is a valid argument. - */ - const char* arg; - - /** - * @brief The length of the option @ref name. - * - * Because @ref name points into the actual @c argv string, the option name may be - * followed by more characters (e.g. other short options in the same short option group). - * This value is the number of bytes (not characters!) that are part of the actual name. - * - * For a short option, this length is always 1. For a long option this length is always - * at least 2 if single minus long options are permitted and at least 3 if they are disabled. - * - * @note - * In the pathological case of a minus within a short option group (e.g. @c -xf-z), this - * length is incorrect, because this case will be misinterpreted as a long option and the - * name will therefore extend to the string's 0-terminator or a following '=" character - * if there is one. This is irrelevant for most uses of @ref name and @c namelen. If you - * really need to distinguish the case of a long and a short option, compare @ref name to - * the @c argv pointers. A long option's @c name is always identical to one of them, - * whereas a short option's is never. - */ - int namelen; - - /** - * @brief Returns Descriptor::type of this Option's Descriptor, or 0 if this Option - * is invalid (unused). - * - * Because this method (and last(), too) can be used even on unused Options with desc==0, you can (provided - * you arrange your types properly) switch on type() without testing validity first. - * @code - * enum OptionType { UNUSED=0, DISABLED=0, ENABLED=1 }; - * enum OptionIndex { FOO }; - * const Descriptor usage[] = { - * { FOO, ENABLED, "", "enable-foo", Arg::None, 0 }, - * { FOO, DISABLED, "", "disable-foo", Arg::None, 0 }, - * { 0, 0, 0, 0, 0, 0 } }; - * ... - * switch(options[FOO].last()->type()) // no validity check required! - * { - * case ENABLED: ... - * case DISABLED: ... // UNUSED==DISABLED ! - * } - * @endcode - */ - int type() const - { - return desc == 0 ? 0 : desc->type; - } - - /** - * @brief Returns Descriptor::index of this Option's Descriptor, or -1 if this Option - * is invalid (unused). - */ - int index() const - { - return desc == 0 ? -1 : (int)desc->index; - } - - /** - * @brief Returns the number of times this Option (or others with the same Descriptor::index) - * occurs in the argument vector. - * - * This corresponds to the number of elements in the linked list this Option is part of. - * It doesn't matter on which element you call count(). The return value is always the same. - * - * Use this to implement cumulative options, such as -v, -vv, -vvv for - * different verbosity levels. - * - * Returns 0 when called for an unused/invalid option. - */ - int count() - { - int c = (desc == 0 ? 0 : 1); - Option* p = first(); - while (!p->isLast()) - { - ++c; - p = p->next_; - }; - return c; - } - - /** - * @brief Returns true iff this is the first element of the linked list. - * - * The first element in the linked list is the first option on the command line - * that has the respective Descriptor::index value. - * - * Returns true for an unused/invalid option. - */ - bool isFirst() const - { - return isTagged(prev_); - } - - /** - * @brief Returns true iff this is the last element of the linked list. - * - * The last element in the linked list is the last option on the command line - * that has the respective Descriptor::index value. - * - * Returns true for an unused/invalid option. - */ - bool isLast() const - { - return isTagged(next_); - } - - /** - * @brief Returns a pointer to the first element of the linked list. - * - * Use this when you want the first occurrence of an option on the command line to - * take precedence. Note that this is not the way most programs handle options. - * You should probably be using last() instead. - * - * @note - * This method may be called on an unused/invalid option and will return a pointer to the - * option itself. - */ - Option* first() - { - Option* p = this; - while (!p->isFirst()) - p = p->prev_; - return p; - } - - /** - * @brief Returns a pointer to the last element of the linked list. - * - * Use this when you want the last occurrence of an option on the command line to - * take precedence. This is the most common way of handling conflicting options. - * - * @note - * This method may be called on an unused/invalid option and will return a pointer to the - * option itself. - * - * @par Tip: - * If you have options with opposite meanings (e.g. @c --enable-foo and @c --disable-foo), you - * can assign them the same Descriptor::index to get them into the same list. Distinguish them by - * Descriptor::type and all you have to do is check last()->type() to get - * the state listed last on the command line. - */ - Option* last() - { - return first()->prevwrap(); - } - - /** - * @brief Returns a pointer to the previous element of the linked list or NULL if - * called on first(). - * - * If called on first() this method returns NULL. Otherwise it will return the - * option with the same Descriptor::index that precedes this option on the command - * line. - */ - Option* prev() - { - return isFirst() ? 0 : prev_; - } - - /** - * @brief Returns a pointer to the previous element of the linked list with wrap-around from - * first() to last(). - * - * If called on first() this method returns last(). Otherwise it will return the - * option with the same Descriptor::index that precedes this option on the command - * line. - */ - Option* prevwrap() - { - return untag(prev_); - } - - /** - * @brief Returns a pointer to the next element of the linked list or NULL if called - * on last(). - * - * If called on last() this method returns NULL. Otherwise it will return the - * option with the same Descriptor::index that follows this option on the command - * line. - */ - Option* next() - { - return isLast() ? 0 : next_; - } - - /** - * @brief Returns a pointer to the next element of the linked list with wrap-around from - * last() to first(). - * - * If called on last() this method returns first(). Otherwise it will return the - * option with the same Descriptor::index that follows this option on the command - * line. - */ - Option* nextwrap() - { - return untag(next_); - } - - /** - * @brief Makes @c new_last the new last() by chaining it into the list after last(). - * - * It doesn't matter which element you call append() on. The new element will always - * be appended to last(). - * - * @attention - * @c new_last must not yet be part of a list, or that list will become corrupted, because - * this method does not unchain @c new_last from an existing list. - */ - void append(Option* new_last) - { - Option* p = last(); - Option* f = first(); - p->next_ = new_last; - new_last->prev_ = p; - new_last->next_ = tag(f); - f->prev_ = tag(new_last); - } - - /** - * @brief Casts from Option to const Option* but only if this Option is valid. - * - * If this Option is valid (i.e. @c desc!=NULL), returns this. - * Otherwise returns NULL. This allows testing an Option directly - * in an if-clause to see if it is used: - * @code - * if (options[CREATE]) - * { - * ... - * } - * @endcode - * It also allows you to write loops like this: - * @code for (Option* opt = options[FILE]; opt; opt = opt->next()) - * fname = opt->arg; ... @endcode - */ - operator const Option*() const - { - return desc ? this : 0; - } - - /** - * @brief Casts from Option to Option* but only if this Option is valid. - * - * If this Option is valid (i.e. @c desc!=NULL), returns this. - * Otherwise returns NULL. This allows testing an Option directly - * in an if-clause to see if it is used: - * @code - * if (options[CREATE]) - * { - * ... - * } - * @endcode - * It also allows you to write loops like this: - * @code for (Option* opt = options[FILE]; opt; opt = opt->next()) - * fname = opt->arg; ... @endcode - */ - operator Option*() - { - return desc ? this : 0; - } - - /** - * @brief Creates a new Option that is a one-element linked list and has NULL - * @ref desc, @ref name, @ref arg and @ref namelen. - */ - Option() : - desc(0), name(0), arg(0), namelen(0) - { - prev_ = tag(this); - next_ = tag(this); - } - - /** - * @brief Creates a new Option that is a one-element linked list and has the given - * values for @ref desc, @ref name and @ref arg. - * - * If @c name_ points at a character other than '-' it will be assumed to refer to a - * short option and @ref namelen will be set to 1. Otherwise the length will extend to - * the first '=' character or the string's 0-terminator. - */ - Option(const Descriptor* desc_, const char* name_, const char* arg_) - { - init(desc_, name_, arg_); - } - - /** - * @brief Makes @c *this a copy of @c orig except for the linked list pointers. - * - * After this operation @c *this will be a one-element linked list. - */ - void operator=(const Option& orig) - { - init(orig.desc, orig.name, orig.arg); - } - - /** - * @brief Makes @c *this a copy of @c orig except for the linked list pointers. - * - * After this operation @c *this will be a one-element linked list. - */ - Option(const Option& orig) - { - init(orig.desc, orig.name, orig.arg); - } + + /** + * @brief Pointer to this Option's Descriptor. + * + * Remember that the first dummy descriptor (see @ref Descriptor::longopt) is used + * for unknown options. + * + * @attention + * @c desc==NULL signals that this Option is unused. This is the default state of + * elements in the result array. You don't need to test @c desc explicitly. You + * can simply write something like this: + * @code + * if (options[CREATE]) + * { + * ... + * } + * @endcode + * This works because of operator const Option*() . + */ + const Descriptor* desc; + + /** + * @brief The name of the option as used on the command line. + * + * The main purpose of this string is to be presented to the user in messages. + * + * In the case of a long option, this is the actual @c argv pointer, i.e. the first + * character is a '-'. In the case of a short option this points to the option + * character within the @c argv string. + * + * Note that in the case of a short option group or an attached option argument, this + * string will contain additional characters following the actual name. Use @ref namelen + * to filter out the actual option name only. + * + */ + const char* name; + + /** + * @brief Pointer to this Option's argument (if any). + * + * NULL if this option has no argument. Do not confuse this with the empty string which + * is a valid argument. + */ + const char* arg; + + /** + * @brief The length of the option @ref name. + * + * Because @ref name points into the actual @c argv string, the option name may be + * followed by more characters (e.g. other short options in the same short option group). + * This value is the number of bytes (not characters!) that are part of the actual name. + * + * For a short option, this length is always 1. For a long option this length is always + * at least 2 if single minus long options are permitted and at least 3 if they are disabled. + * + * @note + * In the pathological case of a minus within a short option group (e.g. @c -xf-z), this + * length is incorrect, because this case will be misinterpreted as a long option and the + * name will therefore extend to the string's 0-terminator or a following '=" character + * if there is one. This is irrelevant for most uses of @ref name and @c namelen. If you + * really need to distinguish the case of a long and a short option, compare @ref name to + * the @c argv pointers. A long option's @c name is always identical to one of them, + * whereas a short option's is never. + */ + int namelen; + + /** + * @brief Returns Descriptor::type of this Option's Descriptor, or 0 if this Option + * is invalid (unused). + * + * Because this method (and last(), too) can be used even on unused Options with desc==0, you can (provided + * you arrange your types properly) switch on type() without testing validity first. + * @code + * enum OptionType { UNUSED=0, DISABLED=0, ENABLED=1 }; + * enum OptionIndex { FOO }; + * const Descriptor usage[] = { + * { FOO, ENABLED, "", "enable-foo", Arg::None, 0 }, + * { FOO, DISABLED, "", "disable-foo", Arg::None, 0 }, + * { 0, 0, 0, 0, 0, 0 } }; + * ... + * switch(options[FOO].last()->type()) // no validity check required! + * { + * case ENABLED: ... + * case DISABLED: ... // UNUSED==DISABLED ! + * } + * @endcode + */ + int type() const + { + return desc == 0 ? 0 : desc->type; + } + + /** + * @brief Returns Descriptor::index of this Option's Descriptor, or -1 if this Option + * is invalid (unused). + */ + int index() const + { + return desc == 0 ? -1 : (int)desc->index; + } + + /** + * @brief Returns the number of times this Option (or others with the same Descriptor::index) + * occurs in the argument vector. + * + * This corresponds to the number of elements in the linked list this Option is part of. + * It doesn't matter on which element you call count(). The return value is always the same. + * + * Use this to implement cumulative options, such as -v, -vv, -vvv for + * different verbosity levels. + * + * Returns 0 when called for an unused/invalid option. + */ + int count() + { + int c = (desc == 0 ? 0 : 1); + Option* p = first(); + while (!p->isLast()) + { + ++c; + p = p->next_; + } + return c; + } + + /** + * @brief Returns true iff this is the first element of the linked list. + * + * The first element in the linked list is the first option on the command line + * that has the respective Descriptor::index value. + * + * Returns true for an unused/invalid option. + */ + bool isFirst() const + { + return isTagged(prev_); + } + + /** + * @brief Returns true iff this is the last element of the linked list. + * + * The last element in the linked list is the last option on the command line + * that has the respective Descriptor::index value. + * + * Returns true for an unused/invalid option. + */ + bool isLast() const + { + return isTagged(next_); + } + + /** + * @brief Returns a pointer to the first element of the linked list. + * + * Use this when you want the first occurrence of an option on the command line to + * take precedence. Note that this is not the way most programs handle options. + * You should probably be using last() instead. + * + * @note + * This method may be called on an unused/invalid option and will return a pointer to the + * option itself. + */ + Option* first() + { + Option* p = this; + while (!p->isFirst()) + { + p = p->prev_; + } + return p; + } + + /** + * @brief Returns a pointer to the last element of the linked list. + * + * Use this when you want the last occurrence of an option on the command line to + * take precedence. This is the most common way of handling conflicting options. + * + * @note + * This method may be called on an unused/invalid option and will return a pointer to the + * option itself. + * + * @par Tip: + * If you have options with opposite meanings (e.g. @c --enable-foo and @c --disable-foo), you + * can assign them the same Descriptor::index to get them into the same list. Distinguish them by + * Descriptor::type and all you have to do is check last()->type() to get + * the state listed last on the command line. + */ + Option* last() + { + return first()->prevwrap(); + } + + /** + * @brief Returns a pointer to the previous element of the linked list or NULL if + * called on first(). + * + * If called on first() this method returns NULL. Otherwise it will return the + * option with the same Descriptor::index that precedes this option on the command + * line. + */ + Option* prev() + { + return isFirst() ? 0 : prev_; + } + + /** + * @brief Returns a pointer to the previous element of the linked list with wrap-around from + * first() to last(). + * + * If called on first() this method returns last(). Otherwise it will return the + * option with the same Descriptor::index that precedes this option on the command + * line. + */ + Option* prevwrap() + { + return untag(prev_); + } + + /** + * @brief Returns a pointer to the next element of the linked list or NULL if called + * on last(). + * + * If called on last() this method returns NULL. Otherwise it will return the + * option with the same Descriptor::index that follows this option on the command + * line. + */ + Option* next() + { + return isLast() ? 0 : next_; + } + + /** + * @brief Returns a pointer to the next element of the linked list with wrap-around from + * last() to first(). + * + * If called on last() this method returns first(). Otherwise it will return the + * option with the same Descriptor::index that follows this option on the command + * line. + */ + Option* nextwrap() + { + return untag(next_); + } + + /** + * @brief Makes @c new_last the new last() by chaining it into the list after last(). + * + * It doesn't matter which element you call append() on. The new element will always + * be appended to last(). + * + * @attention + * @c new_last must not yet be part of a list, or that list will become corrupted, because + * this method does not unchain @c new_last from an existing list. + */ + void append( + Option* new_last) + { + Option* p = last(); + Option* f = first(); + p->next_ = new_last; + new_last->prev_ = p; + new_last->next_ = tag(f); + f->prev_ = tag(new_last); + } + + /** + * @brief Casts from Option to const Option* but only if this Option is valid. + * + * If this Option is valid (i.e. @c desc!=NULL), returns this. + * Otherwise returns NULL. This allows testing an Option directly + * in an if-clause to see if it is used: + * @code + * if (options[CREATE]) + * { + * ... + * } + * @endcode + * It also allows you to write loops like this: + * @code for (Option* opt = options[FILE]; opt; opt = opt->next()) + * fname = opt->arg; ... @endcode + */ + operator const Option*() const + { + return desc ? this : 0; + } + + /** + * @brief Casts from Option to Option* but only if this Option is valid. + * + * If this Option is valid (i.e. @c desc!=NULL), returns this. + * Otherwise returns NULL. This allows testing an Option directly + * in an if-clause to see if it is used: + * @code + * if (options[CREATE]) + * { + * ... + * } + * @endcode + * It also allows you to write loops like this: + * @code for (Option* opt = options[FILE]; opt; opt = opt->next()) + * fname = opt->arg; ... @endcode + */ + operator Option*() + { + return desc ? this : 0; + } + + /** + * @brief Creates a new Option that is a one-element linked list and has NULL + * @ref desc, @ref name, @ref arg and @ref namelen. + */ + Option() + : desc(0) + , name(0) + , arg(0) + , namelen(0) + { + prev_ = tag(this); + next_ = tag(this); + } + + /** + * @brief Creates a new Option that is a one-element linked list and has the given + * values for @ref desc, @ref name and @ref arg. + * + * If @c name_ points at a character other than '-' it will be assumed to refer to a + * short option and @ref namelen will be set to 1. Otherwise the length will extend to + * the first '=' character or the string's 0-terminator. + */ + Option( + const Descriptor* desc_, + const char* name_, + const char* arg_) + { + init(desc_, name_, arg_); + } + + /** + * @brief Makes @c *this a copy of @c orig except for the linked list pointers. + * + * After this operation @c *this will be a one-element linked list. + */ + void operator =( + const Option& orig) + { + init(orig.desc, orig.name, orig.arg); + } + + /** + * @brief Makes @c *this a copy of @c orig except for the linked list pointers. + * + * After this operation @c *this will be a one-element linked list. + */ + Option( + const Option& orig) + { + init(orig.desc, orig.name, orig.arg); + } private: - /** - * @internal - * @brief Sets the fields of this Option to the given values (extracting @c name if necessary). - * - * If @c name_ points at a character other than '-' it will be assumed to refer to a - * short option and @ref namelen will be set to 1. Otherwise the length will extend to - * the first '=' character or the string's 0-terminator. - */ - void init(const Descriptor* desc_, const char* name_, const char* arg_) - { - desc = desc_; - name = name_; - arg = arg_; - prev_ = tag(this); - next_ = tag(this); - namelen = 0; - if (name == 0) - return; - namelen = 1; - if (name[0] != '-') - return; - while (name[namelen] != 0 && name[namelen] != '=') - ++namelen; - } - - static Option* tag(Option* ptr) - { - return (Option*) ((unsigned long long) ptr | 1); - } - - static Option* untag(Option* ptr) - { - return (Option*) ((unsigned long long) ptr & ~1ull); - } - - static bool isTagged(Option* ptr) - { - return ((unsigned long long) ptr & 1); - } + + /** + * @internal + * @brief Sets the fields of this Option to the given values (extracting @c name if necessary). + * + * If @c name_ points at a character other than '-' it will be assumed to refer to a + * short option and @ref namelen will be set to 1. Otherwise the length will extend to + * the first '=' character or the string's 0-terminator. + */ + void init( + const Descriptor* desc_, + const char* name_, + const char* arg_) + { + desc = desc_; + name = name_; + arg = arg_; + prev_ = tag(this); + next_ = tag(this); + namelen = 0; + if (name == 0) + { + return; + } + namelen = 1; + if (name[0] != '-') + { + return; + } + while (name[namelen] != 0 && name[namelen] != '=') + { + ++namelen; + } + } + + static Option* tag( + Option* ptr) + { + return (Option*) ((unsigned long long) ptr | 1); + } + + static Option* untag( + Option* ptr) + { + return (Option*) ((unsigned long long) ptr & ~1ull); + } + + static bool isTagged( + Option* ptr) + { + return ((unsigned long long) ptr & 1); + } + }; /** @@ -885,20 +915,29 @@ class Option */ struct Arg { - //! @brief For options that don't take an argument: Returns ARG_NONE. - static ArgStatus None(const Option&, bool) - { - return ARG_NONE; - } - - //! @brief Returns ARG_OK if the argument is attached and ARG_IGNORE otherwise. - static ArgStatus Optional(const Option& option, bool) - { - if (option.arg && option.name[option.namelen] != 0) - return ARG_OK; - else - return ARG_IGNORE; - } + //! @brief For options that don't take an argument: Returns ARG_NONE. + static ArgStatus None( + const Option&, + bool) + { + return ARG_NONE; + } + + //! @brief Returns ARG_OK if the argument is attached and ARG_IGNORE otherwise. + static ArgStatus Optional( + const Option& option, + bool) + { + if (option.arg && option.name[option.namelen] != 0) + { + return ARG_OK; + } + else + { + return ARG_IGNORE; + } + } + }; /** @@ -912,112 +951,155 @@ struct Arg */ struct Stats { - /** - * @brief Number of elements needed for a @c buffer[] array to be used for - * @ref Parser::parse() "parsing" the same argument vectors that were fed - * into this Stats object. - * - * @note - * This number is always 1 greater than the actual number needed, to give - * you a sentinel element. - */ - unsigned buffer_max; - - /** - * @brief Number of elements needed for an @c options[] array to be used for - * @ref Parser::parse() "parsing" the same argument vectors that were fed - * into this Stats object. - * - * @note - * @li This number is always 1 greater than the actual number needed, to give - * you a sentinel element. - * @li This number depends only on the @c usage, not the argument vectors, because - * the @c options array needs exactly one slot for each possible Descriptor::index. - */ - unsigned options_max; - - /** - * @brief Creates a Stats object with counts set to 1 (for the sentinel element). - */ - Stats() : - buffer_max(1), options_max(1) // 1 more than necessary as sentinel - { - } - - /** - * @brief Creates a new Stats object and immediately updates it for the - * given @c usage and argument vector. You may pass 0 for @c argc and/or @c argv, - * if you just want to update @ref options_max. - * - * @note - * The calls to Stats methods must match the later calls to Parser methods. - * See Parser::parse() for the meaning of the arguments. - */ - Stats(bool gnu, const Descriptor usage[], int argc, const char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) : - buffer_max(1), options_max(1) // 1 more than necessary as sentinel - { - add(gnu, usage, argc, argv, min_abbr_len, single_minus_longopt); - } - - //! @brief Stats(...) with non-const argv. - Stats(bool gnu, const Descriptor usage[], int argc, char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) : - buffer_max(1), options_max(1) // 1 more than necessary as sentinel - { - add(gnu, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); - } - - //! @brief POSIX Stats(...) (gnu==false). - Stats(const Descriptor usage[], int argc, const char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) : - buffer_max(1), options_max(1) // 1 more than necessary as sentinel - { - add(false, usage, argc, argv, min_abbr_len, single_minus_longopt); - } - - //! @brief POSIX Stats(...) (gnu==false) with non-const argv. - Stats(const Descriptor usage[], int argc, char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) : - buffer_max(1), options_max(1) // 1 more than necessary as sentinel - { - add(false, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); - } - - /** - * @brief Updates this Stats object for the - * given @c usage and argument vector. You may pass 0 for @c argc and/or @c argv, - * if you just want to update @ref options_max. - * - * @note - * The calls to Stats methods must match the later calls to Parser methods. - * See Parser::parse() for the meaning of the arguments. - */ - void add(bool gnu, const Descriptor usage[], int argc, const char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false); - - //! @brief add() with non-const argv. - void add(bool gnu, const Descriptor usage[], int argc, char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) - { - add(gnu, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); - } - - //! @brief POSIX add() (gnu==false). - void add(const Descriptor usage[], int argc, const char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) - { - add(false, usage, argc, argv, min_abbr_len, single_minus_longopt); - } - - //! @brief POSIX add() (gnu==false) with non-const argv. - void add(const Descriptor usage[], int argc, char** argv, int min_abbr_len = 0, // - bool single_minus_longopt = false) - { - add(false, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); - } + /** + * @brief Number of elements needed for a @c buffer[] array to be used for + * @ref Parser::parse() "parsing" the same argument vectors that were fed + * into this Stats object. + * + * @note + * This number is always 1 greater than the actual number needed, to give + * you a sentinel element. + */ + unsigned buffer_max; + + /** + * @brief Number of elements needed for an @c options[] array to be used for + * @ref Parser::parse() "parsing" the same argument vectors that were fed + * into this Stats object. + * + * @note + * @li This number is always 1 greater than the actual number needed, to give + * you a sentinel element. + * @li This number depends only on the @c usage, not the argument vectors, because + * the @c options array needs exactly one slot for each possible Descriptor::index. + */ + unsigned options_max; + + /** + * @brief Creates a Stats object with counts set to 1 (for the sentinel element). + */ + Stats() + : buffer_max(1) + , options_max(1) // 1 more than necessary as sentinel + { + } + + /** + * @brief Creates a new Stats object and immediately updates it for the + * given @c usage and argument vector. You may pass 0 for @c argc and/or @c argv, + * if you just want to update @ref options_max. + * + * @note + * The calls to Stats methods must match the later calls to Parser methods. + * See Parser::parse() for the meaning of the arguments. + */ + Stats( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + : buffer_max(1) + , options_max(1) // 1 more than necessary as sentinel + { + add(gnu, usage, argc, argv, min_abbr_len, single_minus_longopt); + } + + //! @brief Stats(...) with non-const argv. + Stats( + bool gnu, + const Descriptor usage[], + int argc, + char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + : buffer_max(1) + , options_max(1) // 1 more than necessary as sentinel + { + add(gnu, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); + } + + //! @brief POSIX Stats(...) (gnu==false). + Stats( + const Descriptor usage[], + int argc, + const char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + : buffer_max(1) + , options_max(1) // 1 more than necessary as sentinel + { + add(false, usage, argc, argv, min_abbr_len, single_minus_longopt); + } + + //! @brief POSIX Stats(...) (gnu==false) with non-const argv. + Stats( + const Descriptor usage[], + int argc, + char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + : buffer_max(1) + , options_max(1) // 1 more than necessary as sentinel + { + add(false, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); + } + + /** + * @brief Updates this Stats object for the + * given @c usage and argument vector. You may pass 0 for @c argc and/or @c argv, + * if you just want to update @ref options_max. + * + * @note + * The calls to Stats methods must match the later calls to Parser methods. + * See Parser::parse() for the meaning of the arguments. + */ + void add( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false); + + //! @brief add() with non-const argv. + void add( + bool gnu, + const Descriptor usage[], + int argc, + char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + { + add(gnu, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); + } + + //! @brief POSIX add() (gnu==false). + void add( + const Descriptor usage[], + int argc, + const char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + { + add(false, usage, argc, argv, min_abbr_len, single_minus_longopt); + } + + //! @brief POSIX add() (gnu==false) with non-const argv. + void add( + const Descriptor usage[], + int argc, + char** argv, + int min_abbr_len = 0, // + bool single_minus_longopt = false) + { + add(false, usage, argc, (const char**) argv, min_abbr_len, single_minus_longopt); + } + private: - class CountOptionsAction; + + class CountOptionsAction; }; /** @@ -1042,310 +1124,411 @@ struct Stats */ class Parser { - int op_count; //!< @internal @brief see optionsCount() - int nonop_count; //!< @internal @brief see nonOptionsCount() - const char** nonop_args; //!< @internal @brief see nonOptions() - bool err; //!< @internal @brief see error() + int op_count; //!< @internal @brief see optionsCount() + int nonop_count; //!< @internal @brief see nonOptionsCount() + const char** nonop_args; //!< @internal @brief see nonOptions() + bool err; //!< @internal @brief see error() + public: - /** - * @brief Creates a new Parser. - */ - Parser() : - op_count(0), nonop_count(0), nonop_args(0), err(false) - { - } - - /** - * @brief Creates a new Parser and immediately parses the given argument vector. - * @copydetails parse() - */ - Parser(bool gnu, const Descriptor usage[], int argc, const char** argv, Option options[], Option buffer[], - int min_abbr_len = 0, bool single_minus_longopt = false, int bufmax = -1) : - op_count(0), nonop_count(0), nonop_args(0), err(false) - { - parse(gnu, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - //! @brief Parser(...) with non-const argv. - Parser(bool gnu, const Descriptor usage[], int argc, char** argv, Option options[], Option buffer[], - int min_abbr_len = 0, bool single_minus_longopt = false, int bufmax = -1) : - op_count(0), nonop_count(0), nonop_args(0), err(false) - { - parse(gnu, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - //! @brief POSIX Parser(...) (gnu==false). - Parser(const Descriptor usage[], int argc, const char** argv, Option options[], Option buffer[], int min_abbr_len = 0, - bool single_minus_longopt = false, int bufmax = -1) : - op_count(0), nonop_count(0), nonop_args(0), err(false) - { - parse(false, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - //! @brief POSIX Parser(...) (gnu==false) with non-const argv. - Parser(const Descriptor usage[], int argc, char** argv, Option options[], Option buffer[], int min_abbr_len = 0, - bool single_minus_longopt = false, int bufmax = -1) : - op_count(0), nonop_count(0), nonop_args(0), err(false) - { - parse(false, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - /** - * @brief Parses the given argument vector. - * - * @param gnu if true, parse() will not stop at the first non-option argument. Instead it will - * reorder arguments so that all non-options are at the end. This is the default behaviour - * of GNU getopt() but is not conforming to POSIX. @n - * Note, that once the argument vector has been reordered, the @c gnu flag will have - * no further effect on this argument vector. So it is enough to pass @c gnu==true when - * creating Stats. - * @param usage Array of Descriptor objects that describe the options to support. The last entry - * of this array must have 0 in all fields. - * @param argc The number of elements from @c argv that are to be parsed. If you pass -1, the number - * will be determined automatically. In that case the @c argv list must end with a NULL - * pointer. - * @param argv The arguments to be parsed. If you pass -1 as @c argc the last pointer in the @c argv - * list must be NULL to mark the end. - * @param options Each entry is the first element of a linked list of Options. Each new option - * that is parsed will be appended to the list specified by that Option's - * Descriptor::index. If an entry is not yet used (i.e. the Option is invalid), - * it will be replaced rather than appended to. @n - * The minimum length of this array is the greatest Descriptor::index value that - * occurs in @c usage @e PLUS ONE. - * @param buffer Each argument that is successfully parsed (including unknown arguments, if they - * have a Descriptor whose CheckArg does not return @ref ARG_ILLEGAL) will be stored in this - * array. parse() scans the array for the first invalid entry and begins writing at that - * index. You can pass @c bufmax to limit the number of options stored. - * @param min_abbr_len Passing a value min_abbr_len > 0 enables abbreviated long - * options. The parser will match a prefix of a long option as if it was - * the full long option (e.g. @c --foob=10 will be interpreted as if it was - * @c --foobar=10 ), as long as the prefix has at least @c min_abbr_len characters - * (not counting the @c -- ) and is unambiguous. - * @n Be careful if combining @c min_abbr_len=1 with @c single_minus_longopt=true - * because the ambiguity check does not consider short options and abbreviated - * single minus long options will take precedence over short options. - * @param single_minus_longopt Passing @c true for this option allows long options to begin with - * a single minus. The double minus form will still be recognized. Note that - * single minus long options take precedence over short options and short option - * groups. E.g. @c -file would be interpreted as @c --file and not as - * -f -i -l -e (assuming a long option named @c "file" exists). - * @param bufmax The greatest index in the @c buffer[] array that parse() will write to is - * @c bufmax-1. If there are more options, they will be processed (in particular - * their CheckArg will be called) but not stored. @n - * If you used Stats::buffer_max to dimension this array, you can pass - * -1 (or not pass @c bufmax at all) which tells parse() that the buffer is - * "large enough". - * @attention - * Remember that @c options and @c buffer store Option @e objects, not pointers. Therefore it - * is not possible for the same object to be in both arrays. For those options that are found in - * both @c buffer[] and @c options[] the respective objects are independent copies. And only the - * objects in @c options[] are properly linked via Option::next() and Option::prev(). - * You can iterate over @c buffer[] to - * process all options in the order they appear in the argument vector, but if you want access to - * the other Options with the same Descriptor::index, then you @e must access the linked list via - * @c options[]. You can get the linked list in options from a buffer object via something like - * @c options[buffer[i].index()]. - */ - void parse(bool gnu, const Descriptor usage[], int argc, const char** argv, Option options[], Option buffer[], - int min_abbr_len = 0, bool single_minus_longopt = false, int bufmax = -1); - - //! @brief parse() with non-const argv. - void parse(bool gnu, const Descriptor usage[], int argc, char** argv, Option options[], Option buffer[], - int min_abbr_len = 0, bool single_minus_longopt = false, int bufmax = -1) - { - parse(gnu, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - //! @brief POSIX parse() (gnu==false). - void parse(const Descriptor usage[], int argc, const char** argv, Option options[], Option buffer[], - int min_abbr_len = 0, bool single_minus_longopt = false, int bufmax = -1) - { - parse(false, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - //! @brief POSIX parse() (gnu==false) with non-const argv. - void parse(const Descriptor usage[], int argc, char** argv, Option options[], Option buffer[], int min_abbr_len = 0, - bool single_minus_longopt = false, int bufmax = -1) - { - parse(false, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); - } - - /** - * @brief Returns the number of valid Option objects in @c buffer[]. - * - * @note - * @li The returned value always reflects the number of Options in the buffer[] array used for - * the most recent call to parse(). - * @li The count (and the buffer[]) includes unknown options if they are collected - * (see Descriptor::longopt). - */ - int optionsCount() - { - return op_count; - } - - /** - * @brief Returns the number of non-option arguments that remained at the end of the - * most recent parse() that actually encountered non-option arguments. - * - * @note - * A parse() that does not encounter non-option arguments will leave this value - * as well as nonOptions() undisturbed. This means you can feed the Parser a - * default argument vector that contains non-option arguments (e.g. a default filename). - * Then you feed it the actual arguments from the user. If the user has supplied at - * least one non-option argument, all of the non-option arguments from the default - * disappear and are replaced by the user's non-option arguments. However, if the - * user does not supply any non-option arguments the defaults will still be in - * effect. - */ - int nonOptionsCount() - { - return nonop_count; - } - - /** - * @brief Returns a pointer to an array of non-option arguments (only valid - * if nonOptionsCount() >0 ). - * - * @note - * @li parse() does not copy arguments, so this pointer points into the actual argument - * vector as passed to parse(). - * @li As explained at nonOptionsCount() this pointer is only changed by parse() calls - * that actually encounter non-option arguments. A parse() call that encounters only - * options, will not change nonOptions(). - */ - const char** nonOptions() - { - return nonop_args; - } - - /** - * @brief Returns nonOptions()[i] (@e without checking if i is in range!). - */ - const char* nonOption(int i) - { - return nonOptions()[i]; - } - - /** - * @brief Returns @c true if an unrecoverable error occurred while parsing options. - * - * An illegal argument to an option (i.e. CheckArg returns @ref ARG_ILLEGAL) is an - * unrecoverable error that aborts the parse. Unknown options are only an error if - * their CheckArg function returns @ref ARG_ILLEGAL. Otherwise they are collected. - * In that case if you want to exit the program if either an illegal argument - * or an unknown option has been passed, use code like this - * - * @code - * if (parser.error() || options[UNKNOWN]) - * exit(1); - * @endcode - * - */ - bool error() - { - return err; - } + /** + * @brief Creates a new Parser. + */ + Parser() + : op_count(0) + , nonop_count(0) + , nonop_args(0) + , err(false) + { + } + + /** + * @brief Creates a new Parser and immediately parses the given argument vector. + * @copydetails parse() + */ + Parser( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + : op_count(0) + , nonop_count(0) + , nonop_args(0) + , err(false) + { + parse(gnu, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + //! @brief Parser(...) with non-const argv. + Parser( + bool gnu, + const Descriptor usage[], + int argc, + char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + : op_count(0) + , nonop_count(0) + , nonop_args(0) + , err(false) + { + parse(gnu, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + //! @brief POSIX Parser(...) (gnu==false). + Parser( + const Descriptor usage[], + int argc, + const char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + : op_count(0) + , nonop_count(0) + , nonop_args(0) + , err(false) + { + parse(false, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + //! @brief POSIX Parser(...) (gnu==false) with non-const argv. + Parser( + const Descriptor usage[], + int argc, + char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + : op_count(0) + , nonop_count(0) + , nonop_args(0) + , err(false) + { + parse(false, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + /** + * @brief Parses the given argument vector. + * + * @param gnu if true, parse() will not stop at the first non-option argument. Instead it will + * reorder arguments so that all non-options are at the end. This is the default behaviour + * of GNU getopt() but is not conforming to POSIX. @n + * Note, that once the argument vector has been reordered, the @c gnu flag will have + * no further effect on this argument vector. So it is enough to pass @c gnu==true when + * creating Stats. + * @param usage Array of Descriptor objects that describe the options to support. The last entry + * of this array must have 0 in all fields. + * @param argc The number of elements from @c argv that are to be parsed. If you pass -1, the number + * will be determined automatically. In that case the @c argv list must end with a NULL + * pointer. + * @param argv The arguments to be parsed. If you pass -1 as @c argc the last pointer in the @c argv + * list must be NULL to mark the end. + * @param options Each entry is the first element of a linked list of Options. Each new option + * that is parsed will be appended to the list specified by that Option's + * Descriptor::index. If an entry is not yet used (i.e. the Option is invalid), + * it will be replaced rather than appended to. @n + * The minimum length of this array is the greatest Descriptor::index value that + * occurs in @c usage @e PLUS ONE. + * @param buffer Each argument that is successfully parsed (including unknown arguments, if they + * have a Descriptor whose CheckArg does not return @ref ARG_ILLEGAL) will be stored in this + * array. parse() scans the array for the first invalid entry and begins writing at that + * index. You can pass @c bufmax to limit the number of options stored. + * @param min_abbr_len Passing a value min_abbr_len > 0 enables abbreviated long + * options. The parser will match a prefix of a long option as if it was + * the full long option (e.g. @c --foob=10 will be interpreted as if it was + * @c --foobar=10 ), as long as the prefix has at least @c min_abbr_len characters + * (not counting the @c -- ) and is unambiguous. + * @n Be careful if combining @c min_abbr_len=1 with @c single_minus_longopt=true + * because the ambiguity check does not consider short options and abbreviated + * single minus long options will take precedence over short options. + * @param single_minus_longopt Passing @c true for this option allows long options to begin with + * a single minus. The double minus form will still be recognized. Note that + * single minus long options take precedence over short options and short option + * groups. E.g. @c -file would be interpreted as @c --file and not as + * -f -i -l -e (assuming a long option named @c "file" exists). + * @param bufmax The greatest index in the @c buffer[] array that parse() will write to is + * @c bufmax-1. If there are more options, they will be processed (in particular + * their CheckArg will be called) but not stored. @n + * If you used Stats::buffer_max to dimension this array, you can pass + * -1 (or not pass @c bufmax at all) which tells parse() that the buffer is + * "large enough". + * @attention + * Remember that @c options and @c buffer store Option @e objects, not pointers. Therefore it + * is not possible for the same object to be in both arrays. For those options that are found in + * both @c buffer[] and @c options[] the respective objects are independent copies. And only the + * objects in @c options[] are properly linked via Option::next() and Option::prev(). + * You can iterate over @c buffer[] to + * process all options in the order they appear in the argument vector, but if you want access to + * the other Options with the same Descriptor::index, then you @e must access the linked list via + * @c options[]. You can get the linked list in options from a buffer object via something like + * @c options[buffer[i].index()]. + */ + void parse( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1); + + //! @brief parse() with non-const argv. + void parse( + bool gnu, + const Descriptor usage[], + int argc, + char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + { + parse(gnu, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + //! @brief POSIX parse() (gnu==false). + void parse( + const Descriptor usage[], + int argc, + const char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + { + parse(false, usage, argc, argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + //! @brief POSIX parse() (gnu==false) with non-const argv. + void parse( + const Descriptor usage[], + int argc, + char** argv, + Option options[], + Option buffer[], + int min_abbr_len = 0, + bool single_minus_longopt = false, + int bufmax = -1) + { + parse(false, usage, argc, (const char**) argv, options, buffer, min_abbr_len, single_minus_longopt, bufmax); + } + + /** + * @brief Returns the number of valid Option objects in @c buffer[]. + * + * @note + * @li The returned value always reflects the number of Options in the buffer[] array used for + * the most recent call to parse(). + * @li The count (and the buffer[]) includes unknown options if they are collected + * (see Descriptor::longopt). + */ + int optionsCount() + { + return op_count; + } + + /** + * @brief Returns the number of non-option arguments that remained at the end of the + * most recent parse() that actually encountered non-option arguments. + * + * @note + * A parse() that does not encounter non-option arguments will leave this value + * as well as nonOptions() undisturbed. This means you can feed the Parser a + * default argument vector that contains non-option arguments (e.g. a default filename). + * Then you feed it the actual arguments from the user. If the user has supplied at + * least one non-option argument, all of the non-option arguments from the default + * disappear and are replaced by the user's non-option arguments. However, if the + * user does not supply any non-option arguments the defaults will still be in + * effect. + */ + int nonOptionsCount() + { + return nonop_count; + } + + /** + * @brief Returns a pointer to an array of non-option arguments (only valid + * if nonOptionsCount() >0 ). + * + * @note + * @li parse() does not copy arguments, so this pointer points into the actual argument + * vector as passed to parse(). + * @li As explained at nonOptionsCount() this pointer is only changed by parse() calls + * that actually encounter non-option arguments. A parse() call that encounters only + * options, will not change nonOptions(). + */ + const char** nonOptions() + { + return nonop_args; + } + + /** + * @brief Returns nonOptions()[i] (@e without checking if i is in range!). + */ + const char* nonOption( + int i) + { + return nonOptions()[i]; + } + + /** + * @brief Returns @c true if an unrecoverable error occurred while parsing options. + * + * An illegal argument to an option (i.e. CheckArg returns @ref ARG_ILLEGAL) is an + * unrecoverable error that aborts the parse. Unknown options are only an error if + * their CheckArg function returns @ref ARG_ILLEGAL. Otherwise they are collected. + * In that case if you want to exit the program if either an illegal argument + * or an unknown option has been passed, use code like this + * + * @code + * if (parser.error() || options[UNKNOWN]) + * exit(1); + * @endcode + * + */ + bool error() + { + return err; + } private: - friend struct Stats; - class StoreOptionAction; - struct Action; - - /** - * @internal - * @brief This is the core function that does all the parsing. - * @retval false iff an unrecoverable error occurred. - */ - static bool workhorse(bool gnu, const Descriptor usage[], int numargs, const char** args, Action& action, - bool single_minus_longopt, bool print_errors, int min_abbr_len); - - /** - * @internal - * @brief Returns true iff @c st1 is a prefix of @c st2 and - * in case @c st2 is longer than @c st1, then - * the first additional character is '='. - * - * @par Examples: - * @code - * streq("foo", "foo=bar") == true - * streq("foo", "foobar") == false - * streq("foo", "foo") == true - * streq("foo=bar", "foo") == false - * @endcode - */ - static bool streq(const char* st1, const char* st2) - { - while (*st1 != 0) - if (*st1++ != *st2++) - return false; - return (*st2 == 0 || *st2 == '='); - } - - /** - * @internal - * @brief Like streq() but handles abbreviations. - * - * Returns true iff @c st1 and @c st2 have a common - * prefix with the following properties: - * @li (if min > 0) its length is at least @c min characters or the same length as @c st1 (whichever is smaller). - * @li (if min <= 0) its length is the same as that of @c st1 - * @li within @c st2 the character following the common prefix is either '=' or end-of-string. - * - * Examples: - * @code - * streqabbr("foo", "foo=bar",) == true - * streqabbr("foo", "fo=bar" , 2) == true - * streqabbr("foo", "fo" , 2) == true - * streqabbr("foo", "fo" , 0) == false - * streqabbr("foo", "f=bar" , 2) == false - * streqabbr("foo", "f" , 2) == false - * streqabbr("fo" , "foo=bar",) == false - * streqabbr("foo", "foobar" ,) == false - * streqabbr("foo", "fobar" ,) == false - * streqabbr("foo", "foo" ,) == true - * @endcode - */ - static bool streqabbr(const char* st1, const char* st2, long long min) - { - const char* st1start = st1; - while (*st1 != 0 && (*st1 == *st2)) - { - ++st1; - ++st2; - } - - return (*st1 == 0 || (min > 0 && (st1 - st1start) >= min)) && (*st2 == 0 || *st2 == '='); - } - - /** - * @internal - * @brief Returns true iff character @c ch is contained in the string @c st. - * - * Returns @c true for @c ch==0 . - */ - static bool instr(char ch, const char* st) - { - while (*st != 0 && *st != ch) - ++st; - return *st == ch; - } - - /** - * @internal - * @brief Rotates args[-count],...,args[-1],args[0] to become - * args[0],args[-count],...,args[-1]. - */ - static void shift(const char** args, int count) - { - for (int i = 0; i > -count; --i) - { - const char* temp = args[i]; - args[i] = args[i - 1]; - args[i - 1] = temp; - } - } + + friend struct Stats; + class StoreOptionAction; + struct Action; + + /** + * @internal + * @brief This is the core function that does all the parsing. + * @retval false iff an unrecoverable error occurred. + */ + static bool workhorse( + bool gnu, + const Descriptor usage[], + int numargs, + const char** args, + Action& action, + bool single_minus_longopt, + bool print_errors, + int min_abbr_len); + + /** + * @internal + * @brief Returns true iff @c st1 is a prefix of @c st2 and + * in case @c st2 is longer than @c st1, then + * the first additional character is '='. + * + * @par Examples: + * @code + * streq("foo", "foo=bar") == true + * streq("foo", "foobar") == false + * streq("foo", "foo") == true + * streq("foo=bar", "foo") == false + * @endcode + */ + static bool streq( + const char* st1, + const char* st2) + { + while (*st1 != 0) + { + if (*st1++ != *st2++) + { + return false; + } + } + return (*st2 == 0 || *st2 == '='); + } + + /** + * @internal + * @brief Like streq() but handles abbreviations. + * + * Returns true iff @c st1 and @c st2 have a common + * prefix with the following properties: + * @li (if min > 0) its length is at least @c min characters or the same length as @c st1 (whichever is smaller). + * @li (if min <= 0) its length is the same as that of @c st1 + * @li within @c st2 the character following the common prefix is either '=' or end-of-string. + * + * Examples: + * @code + * streqabbr("foo", "foo=bar",) == true + * streqabbr("foo", "fo=bar" , 2) == true + * streqabbr("foo", "fo" , 2) == true + * streqabbr("foo", "fo" , 0) == false + * streqabbr("foo", "f=bar" , 2) == false + * streqabbr("foo", "f" , 2) == false + * streqabbr("fo" , "foo=bar",) == false + * streqabbr("foo", "foobar" ,) == false + * streqabbr("foo", "fobar" ,) == false + * streqabbr("foo", "foo" ,) == true + * @endcode + */ + static bool streqabbr( + const char* st1, + const char* st2, + long long min) + { + const char* st1start = st1; + while (*st1 != 0 && (*st1 == *st2)) + { + ++st1; + ++st2; + } + + return (*st1 == 0 || (min > 0 && (st1 - st1start) >= min)) && (*st2 == 0 || *st2 == '='); + } + + /** + * @internal + * @brief Returns true iff character @c ch is contained in the string @c st. + * + * Returns @c true for @c ch==0 . + */ + static bool instr( + char ch, + const char* st) + { + while (*st != 0 && *st != ch) + { + ++st; + } + return *st == ch; + } + + /** + * @internal + * @brief Rotates args[-count],...,args[-1],args[0] to become + * args[0],args[-count],...,args[-1]. + */ + static void shift( + const char** args, + int count) + { + for (int i = 0; i > -count; --i) + { + const char* temp = args[i]; + args[i] = args[i - 1]; + args[i - 1] = temp; + } + } + }; /** @@ -1355,33 +1538,37 @@ class Parser */ struct Parser::Action { - /** - * @brief Called by Parser::workhorse() for each Option that has been successfully - * parsed (including unknown - * options if they have a Descriptor whose Descriptor::check_arg does not return - * @ref ARG_ILLEGAL. - * - * Returns @c false iff a fatal error has occured and the parse should be aborted. - */ - virtual bool perform(Option&) - { - return true; - } - - /** - * @brief Called by Parser::workhorse() after finishing the parse. - * @param numargs the number of non-option arguments remaining - * @param args pointer to the first remaining non-option argument (if numargs > 0). - * - * @return - * @c false iff a fatal error has occurred. - */ - virtual bool finished(int numargs, const char** args) - { - (void) numargs; - (void) args; - return true; - } + /** + * @brief Called by Parser::workhorse() for each Option that has been successfully + * parsed (including unknown + * options if they have a Descriptor whose Descriptor::check_arg does not return + * @ref ARG_ILLEGAL. + * + * Returns @c false iff a fatal error has occured and the parse should be aborted. + */ + virtual bool perform( + Option&) + { + return true; + } + + /** + * @brief Called by Parser::workhorse() after finishing the parse. + * @param numargs the number of non-option arguments remaining + * @param args pointer to the first remaining non-option argument (if numargs > 0). + * + * @return + * @c false iff a fatal error has occurred. + */ + virtual bool finished( + int numargs, + const char** args) + { + (void) numargs; + (void) args; + return true; + } + }; /** @@ -1389,26 +1576,33 @@ struct Parser::Action * @brief An Action to pass to Parser::workhorse() that will increment a counter for * each parsed Option. */ -class Stats::CountOptionsAction: public Parser::Action +class Stats::CountOptionsAction : public Parser::Action { - unsigned* buffer_max; + unsigned* buffer_max; + public: - /** - * Creates a new CountOptionsAction that will increase @c *buffer_max_ for each - * parsed Option. - */ - CountOptionsAction(unsigned* buffer_max_) : - buffer_max(buffer_max_) - { - } - - bool perform(Option&) - { - if (*buffer_max == 0x7fffffff) - return false; // overflow protection: don't accept number of options that doesn't fit signed int - ++*buffer_max; - return true; - } + + /** + * Creates a new CountOptionsAction that will increase @c *buffer_max_ for each + * parsed Option. + */ + CountOptionsAction( + unsigned* buffer_max_) + : buffer_max(buffer_max_) + { + } + + bool perform( + Option&) + { + if (*buffer_max == 0x7fffffff) + { + return false; // overflow protection: don't accept number of options that doesn't fit signed int + } + ++*buffer_max; + return true; + } + }; /** @@ -1416,268 +1610,357 @@ class Stats::CountOptionsAction: public Parser::Action * @brief An Action to pass to Parser::workhorse() that will store each parsed Option in * appropriate arrays (see Parser::parse()). */ -class Parser::StoreOptionAction: public Parser::Action +class Parser::StoreOptionAction : public Parser::Action { - Parser& parser; - Option* options; - Option* buffer; - int bufmax; //! Number of slots in @c buffer. @c -1 means "large enough". -public: - /** - * @brief Creates a new StoreOption action. - * @param parser_ the parser whose op_count should be updated. - * @param options_ each Option @c o is chained into the linked list @c options_[o.desc->index] - * @param buffer_ each Option is appended to this array as long as there's a free slot. - * @param bufmax_ number of slots in @c buffer_. @c -1 means "large enough". - */ - StoreOptionAction(Parser& parser_, Option options_[], Option buffer_[], int bufmax_) : - parser(parser_), options(options_), buffer(buffer_), bufmax(bufmax_) - { - // find first empty slot in buffer (if any) - int bufidx = 0; - while ((bufmax < 0 || bufidx < bufmax) && buffer[bufidx]) - ++bufidx; - - // set parser's optionCount - parser.op_count = bufidx; - } - - bool perform(Option& option) - { - if (bufmax < 0 || parser.op_count < bufmax) - { - if (parser.op_count == 0x7fffffff) - return false; // overflow protection: don't accept number of options that doesn't fit signed int - - buffer[parser.op_count] = option; - int idx = buffer[parser.op_count].desc->index; - if (options[idx]) - options[idx].append(buffer[parser.op_count]); - else - options[idx] = buffer[parser.op_count]; - ++parser.op_count; - } - return true; // NOTE: an option that is discarded because of a full buffer is not fatal - } - - bool finished(int numargs, const char** args) - { - // only overwrite non-option argument list if there's at least 1 - // new non-option argument. Otherwise we keep the old list. This - // makes it easy to use default non-option arguments. - if (numargs > 0) - { - parser.nonop_count = numargs; - parser.nonop_args = args; - } - - return true; - } -}; + Parser& parser; + Option* options; + Option* buffer; + int bufmax; //! Number of slots in @c buffer. @c -1 means "large enough". -inline void Parser::parse(bool gnu, const Descriptor usage[], int argc, const char** argv, Option options[], - Option buffer[], int min_abbr_len, bool single_minus_longopt, int bufmax) -{ - StoreOptionAction action(*this, options, buffer, bufmax); - err = !workhorse(gnu, usage, argc, argv, action, single_minus_longopt, true, min_abbr_len); -} +public: -inline void Stats::add(bool gnu, const Descriptor usage[], int argc, const char** argv, int min_abbr_len, - bool single_minus_longopt) -{ - // determine size of options array. This is the greatest index used in the usage + 1 - int i = 0; - while (usage[i].shortopt != 0) - { - if (usage[i].index + 1 >= options_max) - options_max = (usage[i].index + 1) + 1; // 1 more than necessary as sentinel - - ++i; - } - - CountOptionsAction action(&buffer_max); - Parser::workhorse(gnu, usage, argc, argv, action, single_minus_longopt, false, min_abbr_len); -} + /** + * @brief Creates a new StoreOption action. + * @param parser_ the parser whose op_count should be updated. + * @param options_ each Option @c o is chained into the linked list @c options_[o.desc->index] + * @param buffer_ each Option is appended to this array as long as there's a free slot. + * @param bufmax_ number of slots in @c buffer_. @c -1 means "large enough". + */ + StoreOptionAction( + Parser& parser_, + Option options_[], + Option buffer_[], + int bufmax_) + : parser(parser_) + , options(options_) + , buffer(buffer_) + , bufmax(bufmax_) + { + // find first empty slot in buffer (if any) + int bufidx = 0; + while ((bufmax < 0 || bufidx < bufmax) && buffer[bufidx]) + { + ++bufidx; + } -inline bool Parser::workhorse(bool gnu, const Descriptor usage[], int numargs, const char** args, Action& action, - bool single_minus_longopt, bool print_errors, int min_abbr_len) -{ - // protect against NULL pointer - if (args == 0) - numargs = 0; + // set parser's optionCount + parser.op_count = bufidx; + } - int nonops = 0; + bool perform( + Option& option) + { + if (bufmax < 0 || parser.op_count < bufmax) + { + if (parser.op_count == 0x7fffffff) + { + return false; // overflow protection: don't accept number of options that doesn't fit signed int - while (numargs != 0 && *args != 0) - { - const char* param = *args; // param can be --long-option, -srto or non-option argument + } + buffer[parser.op_count] = option; + int idx = buffer[parser.op_count].desc->index; + if (options[idx]) + { + options[idx].append(buffer[parser.op_count]); + } + else + { + options[idx] = buffer[parser.op_count]; + } + ++parser.op_count; + } + return true; // NOTE: an option that is discarded because of a full buffer is not fatal + } - // in POSIX mode the first non-option argument terminates the option list - // a lone minus character is a non-option argument - if (param[0] != '-' || param[1] == 0) + bool finished( + int numargs, + const char** args) { - if (gnu) - { - ++nonops; - ++args; + // only overwrite non-option argument list if there's at least 1 + // new non-option argument. Otherwise we keep the old list. This + // makes it easy to use default non-option arguments. if (numargs > 0) - --numargs; - continue; - } - else - break; - } + { + parser.nonop_count = numargs; + parser.nonop_args = args; + } - // -- terminates the option list. The -- itself is skipped. - if (param[1] == '-' && param[2] == 0) - { - shift(args, nonops); - ++args; - if (numargs > 0) - --numargs; - break; + return true; } - bool handle_short_options; - const char* longopt_name; - if (param[1] == '-') // if --long-option +}; + +inline void Parser::parse( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + Option options[], + Option buffer[], + int min_abbr_len, + bool single_minus_longopt, + int bufmax) +{ + StoreOptionAction action(*this, options, buffer, bufmax); + err = !workhorse(gnu, usage, argc, argv, action, single_minus_longopt, true, min_abbr_len); +} + +inline void Stats::add( + bool gnu, + const Descriptor usage[], + int argc, + const char** argv, + int min_abbr_len, + bool single_minus_longopt) +{ + // determine size of options array. This is the greatest index used in the usage + 1 + int i = 0; + while (usage[i].shortopt != 0) { - handle_short_options = false; - longopt_name = param + 2; + if (usage[i].index + 1 >= options_max) + { + options_max = (usage[i].index + 1) + 1; // 1 more than necessary as sentinel + + } + ++i; } - else + + CountOptionsAction action(&buffer_max); + Parser::workhorse(gnu, usage, argc, argv, action, single_minus_longopt, false, min_abbr_len); +} + +inline bool Parser::workhorse( + bool gnu, + const Descriptor usage[], + int numargs, + const char** args, + Action& action, + bool single_minus_longopt, + bool print_errors, + int min_abbr_len) +{ + // protect against NULL pointer + if (args == 0) { - handle_short_options = true; - longopt_name = param + 1; //for testing a potential -long-option + numargs = 0; } - bool try_single_minus_longopt = single_minus_longopt; - bool have_more_args = (numargs > 1 || numargs < 0); // is referencing argv[1] valid? + int nonops = 0; - do // loop over short options in group, for long options the body is executed only once + while (numargs != 0 && *args != 0) { - int idx; + const char* param = *args; // param can be --long-option, -srto or non-option argument - const char* optarg; - - /******************** long option **********************/ - if (handle_short_options == false || try_single_minus_longopt) - { - idx = 0; - while (usage[idx].longopt != 0 && !streq(usage[idx].longopt, longopt_name)) - ++idx; + // in POSIX mode the first non-option argument terminates the option list + // a lone minus character is a non-option argument + if (param[0] != '-' || param[1] == 0) + { + if (gnu) + { + ++nonops; + ++args; + if (numargs > 0) + { + --numargs; + } + continue; + } + else + { + break; + } + } - if (usage[idx].longopt == 0 && min_abbr_len > 0) // if we should try to match abbreviated long options + // -- terminates the option list. The -- itself is skipped. + if (param[1] == '-' && param[2] == 0) { - int i1 = 0; - while (usage[i1].longopt != 0 && !streqabbr(usage[i1].longopt, longopt_name, min_abbr_len)) - ++i1; - if (usage[i1].longopt != 0) - { // now test if the match is unambiguous by checking for another match - int i2 = i1 + 1; - while (usage[i2].longopt != 0 && !streqabbr(usage[i2].longopt, longopt_name, min_abbr_len)) - ++i2; - - if (usage[i2].longopt == 0) // if there was no second match it's unambiguous, so accept i1 as idx - idx = i1; - } + shift(args, nonops); + ++args; + if (numargs > 0) + { + --numargs; + } + break; } - // if we found something, disable handle_short_options (only relevant if single_minus_longopt) - if (usage[idx].longopt != 0) - handle_short_options = false; + bool handle_short_options; + const char* longopt_name; + if (param[1] == '-') // if --long-option + { + handle_short_options = false; + longopt_name = param + 2; + } + else + { + handle_short_options = true; + longopt_name = param + 1; //for testing a potential -long-option + } - try_single_minus_longopt = false; // prevent looking for longopt in the middle of shortopt group + bool try_single_minus_longopt = single_minus_longopt; + bool have_more_args = (numargs > 1 || numargs < 0); // is referencing argv[1] valid? - optarg = longopt_name; - while (*optarg != 0 && *optarg != '=') - ++optarg; - if (*optarg == '=') // attached argument - ++optarg; - else - // possibly detached argument - optarg = (have_more_args ? args[1] : 0); - } - - /************************ short option ***********************************/ - if (handle_short_options) - { - if (*++param == 0) // point at the 1st/next option character - break; // end of short option group - - idx = 0; - while (usage[idx].shortopt != 0 && !instr(*param, usage[idx].shortopt)) - ++idx; - - if (param[1] == 0) // if the potential argument is separate - optarg = (have_more_args ? args[1] : 0); - else - // if the potential argument is attached - optarg = param + 1; - } - - const Descriptor* descriptor = &usage[idx]; - - if (descriptor->shortopt == 0) /************** unknown option ********************/ - { - // look for dummy entry (shortopt == "" and longopt == "") to use as Descriptor for unknown options - idx = 0; - while (usage[idx].shortopt != 0 && (usage[idx].shortopt[0] != 0 || usage[idx].longopt[0] != 0)) - ++idx; - descriptor = (usage[idx].shortopt == 0 ? 0 : &usage[idx]); - } - - if (descriptor != 0) - { - Option option(descriptor, param, optarg); - switch (descriptor->check_arg(option, print_errors)) + do // loop over short options in group, for long options the body is executed only once { - case ARG_ILLEGAL: - return false; // fatal - case ARG_OK: - // skip one element of the argument vector, if it's a separated argument - if (optarg != 0 && have_more_args && optarg == args[1]) + int idx = 0; + + const char* optarg = nullptr; + + /******************** long option **********************/ + if (handle_short_options == false || try_single_minus_longopt) + { + idx = 0; + while (usage[idx].longopt != 0 && !streq(usage[idx].longopt, longopt_name)) + { + ++idx; + } + + if (usage[idx].longopt == 0 && min_abbr_len > 0) // if we should try to match abbreviated long options + { + int i1 = 0; + while (usage[i1].longopt != 0 && !streqabbr(usage[i1].longopt, longopt_name, min_abbr_len)) + { + ++i1; + } + if (usage[i1].longopt != 0) + { + // now test if the match is unambiguous by checking for another match + int i2 = i1 + 1; + while (usage[i2].longopt != 0 && !streqabbr(usage[i2].longopt, longopt_name, min_abbr_len)) + { + ++i2; + } + + if (usage[i2].longopt == 0) // if there was no second match it's unambiguous, so accept i1 as idx + { + idx = i1; + } + } + } + + // if we found something, disable handle_short_options (only relevant if single_minus_longopt) + if (usage[idx].longopt != 0) + { + handle_short_options = false; + } + + try_single_minus_longopt = false; // prevent looking for longopt in the middle of shortopt group + + optarg = longopt_name; + while (*optarg != 0 && *optarg != '=') + { + ++optarg; + } + if (*optarg == '=') // attached argument + { + ++optarg; + } + else + { + // possibly detached argument + optarg = (have_more_args ? args[1] : 0); + } + } + + /************************ short option ***********************************/ + if (handle_short_options) + { + if (*++param == 0) // point at the 1st/next option character + { + break; // end of short option group + + } + idx = 0; + while (usage[idx].shortopt != 0 && !instr(*param, usage[idx].shortopt)) + { + ++idx; + } + + if (param[1] == 0) // if the potential argument is separate + { + optarg = (have_more_args ? args[1] : 0); + } + else + { + // if the potential argument is attached + optarg = param + 1; + } + } + + const Descriptor* descriptor = &usage[idx]; + + if (descriptor->shortopt == 0) /************** unknown option ********************/ + { + // look for dummy entry (shortopt == "" and longopt == "") to use as Descriptor for unknown options + idx = 0; + while (usage[idx].shortopt != 0 && (usage[idx].shortopt[0] != 0 || usage[idx].longopt[0] != 0)) + { + ++idx; + } + descriptor = (usage[idx].shortopt == 0 ? 0 : &usage[idx]); + } + + if (descriptor != 0) { - shift(args, nonops); - if (numargs > 0) - --numargs; - ++args; + Option option(descriptor, param, optarg); + switch (descriptor->check_arg(option, print_errors)) + { + case ARG_ILLEGAL: + return false; // fatal + case ARG_OK: + // skip one element of the argument vector, if it's a separated argument + if (optarg != 0 && have_more_args && optarg == args[1]) + { + shift(args, nonops); + if (numargs > 0) + { + --numargs; + } + ++args; + } + + // No further short options are possible after an argument + handle_short_options = false; + + break; + case ARG_IGNORE: + case ARG_NONE: + option.arg = 0; + break; + } + + if (!action.perform(option)) + { + return false; + } } - // No further short options are possible after an argument - handle_short_options = false; + } while (handle_short_options); - break; - case ARG_IGNORE: - case ARG_NONE: - option.arg = 0; - break; + shift(args, nonops); + ++args; + if (numargs > 0) + { + --numargs; } - if (!action.perform(option)) - return false; - } + } // while - } while (handle_short_options); - - shift(args, nonops); - ++args; - if (numargs > 0) - --numargs; - - } // while - - if (numargs > 0 && *args == 0) // It's a bug in the caller if numargs is greater than the actual number - numargs = 0; // of arguments, but as a service to the user we fix this if we spot it. + if (numargs > 0 && *args == 0) // It's a bug in the caller if numargs is greater than the actual number + { + numargs = 0; // of arguments, but as a service to the user we fix this if we spot it. - if (numargs < 0) // if we don't know the number of remaining non-option arguments - { // we need to count them - numargs = 0; - while (args[numargs] != 0) - ++numargs; - } + } + if (numargs < 0) // if we don't know the number of remaining non-option arguments + { + // we need to count them + numargs = 0; + while (args[numargs] != 0) + { + ++numargs; + } + } - return action.finished(numargs + nonops, args - nonops); + return action.finished(numargs + nonops, args - nonops); } /** @@ -1686,893 +1969,1016 @@ inline bool Parser::workhorse(bool gnu, const Descriptor usage[], int numargs, c */ struct PrintUsageImplementation { - /** - * @internal - * @brief Interface for Functors that write (part of) a string somewhere. - */ - struct IStringWriter - { /** - * @brief Writes the given number of chars beginning at the given pointer somewhere. + * @internal + * @brief Interface for Functors that write (part of) a string somewhere. */ - virtual void operator()(const char*, int) + struct IStringWriter { - } - }; + /** + * @brief Writes the given number of chars beginning at the given pointer somewhere. + */ + virtual void operator ()( + const char*, + int) + { + } - /** - * @internal - * @brief Encapsulates a function with signature func(string, size) where - * string can be initialized with a const char* and size with an int. - */ - template - struct FunctionWriter: public IStringWriter - { - Function* write; + }; - virtual void operator()(const char* str, int size) + /** + * @internal + * @brief Encapsulates a function with signature func(string, size) where + * string can be initialized with a const char* and size with an int. + */ + template + struct FunctionWriter : public IStringWriter { - (*write)(str, size); - } + Function* write; - FunctionWriter(Function* w) : - write(w) - { - } - }; + virtual void operator ()( + const char* str, + int size) + { + (*write)(str, size); + } - /** - * @internal - * @brief Encapsulates a reference to an object with a write(string, size) - * method like that of @c std::ostream. - */ - template - struct OStreamWriter: public IStringWriter - { - OStream& ostream; + FunctionWriter( + Function* w) + : write(w) + { + } - virtual void operator()(const char* str, int size) - { - ostream.write(str, size); - } + }; - OStreamWriter(OStream& o) : - ostream(o) + /** + * @internal + * @brief Encapsulates a reference to an object with a write(string, size) + * method like that of @c std::ostream. + */ + template + struct OStreamWriter : public IStringWriter { - } - }; + OStream& ostream; - /** - * @internal - * @brief Like OStreamWriter but encapsulates a @c const reference, which is - * typically a temporary object of a user class. - */ - template - struct TemporaryWriter: public IStringWriter - { - const Temporary& userstream; - - virtual void operator()(const char* str, int size) - { - userstream.write(str, size); - } + virtual void operator ()( + const char* str, + int size) + { + ostream.write(str, size); + } - TemporaryWriter(const Temporary& u) : - userstream(u) - { - } - }; + OStreamWriter( + OStream& o) + : ostream(o) + { + } - /** - * @internal - * @brief Encapsulates a function with the signature func(fd, string, size) (the - * signature of the @c write() system call) - * where fd can be initialized from an int, string from a const char* and size from an int. - */ - template - struct SyscallWriter: public IStringWriter - { - Syscall* write; - int fd; + }; - virtual void operator()(const char* str, int size) + /** + * @internal + * @brief Like OStreamWriter but encapsulates a @c const reference, which is + * typically a temporary object of a user class. + */ + template + struct TemporaryWriter : public IStringWriter { - (*write)(fd, str, size); - } + const Temporary& userstream; - SyscallWriter(Syscall* w, int f) : - write(w), fd(f) - { - } - }; + virtual void operator ()( + const char* str, + int size) + { + userstream.write(str, size); + } + + TemporaryWriter( + const Temporary& u) + : userstream(u) + { + } - /** - * @internal - * @brief Encapsulates a function with the same signature as @c std::fwrite(). - */ - template - struct StreamWriter: public IStringWriter - { - Function* fwrite; - Stream* stream; - - virtual void operator()(const char* str, int size) - { - (*fwrite)(str, size, 1, stream); - } - - StreamWriter(Function* w, Stream* s) : - fwrite(w), stream(s) - { - } - }; - - /** - * @internal - * @brief Sets i1 = max(i1, i2) - */ - static void upmax(int& i1, int i2) - { - i1 = (i1 >= i2 ? i1 : i2); - } - - /** - * @internal - * @brief Moves the "cursor" to column @c want_x assuming it is currently at column @c x - * and sets @c x=want_x . - * If x > want_x , a line break is output before indenting. - * - * @param write Spaces and possibly a line break are written via this functor to get - * the desired indentation @c want_x . - * @param[in,out] x the current indentation. Set to @c want_x by this method. - * @param want_x the desired indentation. - */ - static void indent(IStringWriter& write, int& x, int want_x) - { - int indent = want_x - x; - if (indent < 0) - { - write("\n", 1); - indent = want_x; - } - - if (indent > 0) - { - char space = ' '; - for (int i = 0; i < indent; ++i) - write(&space, 1); - x = want_x; - } - } - - /** - * @brief Returns true if ch is the unicode code point of a wide character. - * - * @note - * The following character ranges are treated as wide - * @code - * 1100..115F - * 2329..232A (just 2 characters!) - * 2E80..A4C6 except for 303F - * A960..A97C - * AC00..D7FB - * F900..FAFF - * FE10..FE6B - * FF01..FF60 - * FFE0..FFE6 - * 1B000...... - * @endcode - */ - static bool isWideChar(unsigned ch) - { - if (ch == 0x303F) - return false; - - return ((0x1100 <= ch && ch <= 0x115F) || (0x2329 <= ch && ch <= 0x232A) || (0x2E80 <= ch && ch <= 0xA4C6) - || (0xA960 <= ch && ch <= 0xA97C) || (0xAC00 <= ch && ch <= 0xD7FB) || (0xF900 <= ch && ch <= 0xFAFF) - || (0xFE10 <= ch && ch <= 0xFE6B) || (0xFF01 <= ch && ch <= 0xFF60) || (0xFFE0 <= ch && ch <= 0xFFE6) - || (0x1B000 <= ch)); - } - - /** - * @internal - * @brief Splits a @c Descriptor[] array into tables, rows, lines and columns and - * iterates over these components. - * - * The top-level organizational unit is the @e table. - * A table begins at a Descriptor with @c help!=NULL and extends up to - * a Descriptor with @c help==NULL. - * - * A table consists of @e rows. Due to line-wrapping and explicit breaks - * a row may take multiple lines on screen. Rows within the table are separated - * by \\n. They never cross Descriptor boundaries. This means a row ends either - * at \\n or the 0 at the end of the help string. - * - * A row consists of columns/cells. Columns/cells within a row are separated by \\t. - * Line breaks within a cell are marked by \\v. - * - * Rows in the same table need not have the same number of columns/cells. The - * extreme case are interjections, which are rows that contain neither \\t nor \\v. - * These are NOT treated specially by LinePartIterator, but they are treated - * specially by printUsage(). - * - * LinePartIterator iterates through the usage at 3 levels: table, row and part. - * Tables and rows are as described above. A @e part is a line within a cell. - * LinePartIterator iterates through 1st parts of all cells, then through the 2nd - * parts of all cells (if any),... @n - * Example: The row "1 \v 3 \t 2 \v 4" has 2 cells/columns and 4 parts. - * The parts will be returned in the order 1, 2, 3, 4. - * - * It is possible that some cells have fewer parts than others. In this case - * LinePartIterator will "fill up" these cells with 0-length parts. IOW, LinePartIterator - * always returns the same number of parts for each column. Note that this is different - * from the way rows and columns are handled. LinePartIterator does @e not guarantee that - * the same number of columns will be returned for each row. - * - */ - class LinePartIterator - { - const Descriptor* tablestart; //!< The 1st descriptor of the current table. - const Descriptor* rowdesc; //!< The Descriptor that contains the current row. - const char* rowstart; //!< Ptr to 1st character of current row within rowdesc->help. - const char* ptr; //!< Ptr to current part within the current row. - int col; //!< Index of current column. - int len; //!< Length of the current part (that ptr points at) in BYTES - int screenlen; //!< Length of the current part in screen columns (taking narrow/wide chars into account). - int max_line_in_block; //!< Greatest index of a line within the block. This is the number of \\v within the cell with the most \\vs. - int line_in_block; //!< Line index within the current cell of the current part. - int target_line_in_block; //!< Line index of the parts we should return to the user on this iteration. - bool hit_target_line; //!< Flag whether we encountered a part with line index target_line_in_block in the current cell. + }; /** - * @brief Determines the byte and character lengths of the part at @ref ptr and - * stores them in @ref len and @ref screenlen respectively. + * @internal + * @brief Encapsulates a function with the signature func(fd, string, size) (the + * signature of the @c write() system call) + * where fd can be initialized from an int, string from a const char* and size from an int. */ - void update_length() - { - screenlen = 0; - for (len = 0; ptr[len] != 0 && ptr[len] != '\v' && ptr[len] != '\t' && ptr[len] != '\n'; ++len) - { - ++screenlen; - unsigned ch = (unsigned char) ptr[len]; - if (ch > 0xC1) // everything <= 0xC1 (yes, even 0xC1 itself) is not a valid UTF-8 start byte + template + struct SyscallWriter : public IStringWriter + { + Syscall* write; + int fd; + + virtual void operator ()( + const char* str, + int size) { - // int __builtin_clz (unsigned int x) - // Returns the number of leading 0-bits in x, starting at the most significant bit - unsigned mask = (unsigned) -1 >> __builtin_clz(ch ^ 0xff); - ch = ch & mask; // mask out length bits, we don't verify their correctness - while (((unsigned char) ptr[len + 1] ^ 0x80) <= 0x3F) // while next byte is continuation byte - { - ch = (ch << 6) ^ (unsigned char) ptr[len + 1] ^ 0x80; // add continuation to char code - ++len; - } - // ch is the decoded unicode code point - if (ch >= 0x1100 && isWideChar(ch)) // the test for 0x1100 is here to avoid the function call in the Latin case - ++screenlen; + (*write)(fd, str, size); } - } - } - public: - //! @brief Creates an iterator for @c usage. - LinePartIterator(const Descriptor usage[]) : - tablestart(usage), rowdesc(0), rowstart(0), ptr(0), col(-1), len(0), max_line_in_block(0), line_in_block(0), - target_line_in_block(0), hit_target_line(true) - { - } + SyscallWriter( + Syscall* w, + int f) + : write(w) + , fd(f) + { + } + + }; /** - * @brief Moves iteration to the next table (if any). Has to be called once on a new - * LinePartIterator to move to the 1st table. - * @retval false if moving to next table failed because no further table exists. + * @internal + * @brief Encapsulates a function with the same signature as @c std::fwrite(). */ - bool nextTable() + template + struct StreamWriter : public IStringWriter { - // If this is NOT the first time nextTable() is called after the constructor, - // then skip to the next table break (i.e. a Descriptor with help == 0) - if (rowdesc != 0) - { - while (tablestart->help != 0 && tablestart->shortopt != 0) - ++tablestart; - } + Function* fwrite; + Stream* stream; + + virtual void operator ()( + const char* str, + int size) + { + (*fwrite)(str, size, 1, stream); + } - // Find the next table after the break (if any) - while (tablestart->help == 0 && tablestart->shortopt != 0) - ++tablestart; + StreamWriter( + Function* w, + Stream* s) + : fwrite(w) + , stream(s) + { + } - restartTable(); - return rowstart != 0; - } + }; /** - * @brief Reset iteration to the beginning of the current table. + * @internal + * @brief Sets i1 = max(i1, i2) */ - void restartTable() + static void upmax( + int& i1, + int i2) { - rowdesc = tablestart; - rowstart = tablestart->help; - ptr = 0; + i1 = (i1 >= i2 ? i1 : i2); } /** - * @brief Moves iteration to the next row (if any). Has to be called once after each call to - * @ref nextTable() to move to the 1st row of the table. - * @retval false if moving to next row failed because no further row exists. + * @internal + * @brief Moves the "cursor" to column @c want_x assuming it is currently at column @c x + * and sets @c x=want_x . + * If x > want_x , a line break is output before indenting. + * + * @param write Spaces and possibly a line break are written via this functor to get + * the desired indentation @c want_x . + * @param[in,out] x the current indentation. Set to @c want_x by this method. + * @param want_x the desired indentation. */ - bool nextRow() + static void indent( + IStringWriter& write, + int& x, + int want_x) { - if (ptr == 0) - { - restartRow(); - return rowstart != 0; - } - - while (*ptr != 0 && *ptr != '\n') - ++ptr; - - if (*ptr == 0) - { - if ((rowdesc + 1)->help == 0) // table break - return false; - - ++rowdesc; - rowstart = rowdesc->help; - } - else // if (*ptr == '\n') - { - rowstart = ptr + 1; - } + int indent = want_x - x; + if (indent < 0) + { + write("\n", 1); + indent = want_x; + } - restartRow(); - return true; + if (indent > 0) + { + char space = ' '; + for (int i = 0; i < indent; ++i) + { + write(&space, 1); + } + x = want_x; + } } /** - * @brief Reset iteration to the beginning of the current row. + * @brief Returns true if ch is the unicode code point of a wide character. + * + * @note + * The following character ranges are treated as wide + * @code + * 1100..115F + * 2329..232A (just 2 characters!) + * 2E80..A4C6 except for 303F + * A960..A97C + * AC00..D7FB + * F900..FAFF + * FE10..FE6B + * FF01..FF60 + * FFE0..FFE6 + * 1B000...... + * @endcode */ - void restartRow() + static bool isWideChar( + unsigned ch) { - ptr = rowstart; - col = -1; - len = 0; - screenlen = 0; - max_line_in_block = 0; - line_in_block = 0; - target_line_in_block = 0; - hit_target_line = true; + if (ch == 0x303F) + { + return false; + } + + return ((0x1100 <= ch && ch <= 0x115F) || (0x2329 <= ch && ch <= 0x232A) || (0x2E80 <= ch && ch <= 0xA4C6) + || (0xA960 <= ch && ch <= 0xA97C) || (0xAC00 <= ch && ch <= 0xD7FB) || (0xF900 <= ch && ch <= 0xFAFF) + || (0xFE10 <= ch && ch <= 0xFE6B) || (0xFF01 <= ch && ch <= 0xFF60) || (0xFFE0 <= ch && ch <= 0xFFE6) + || (0x1B000 <= ch)); } /** - * @brief Moves iteration to the next part (if any). Has to be called once after each call to - * @ref nextRow() to move to the 1st part of the row. - * @retval false if moving to next part failed because no further part exists. + * @internal + * @brief Splits a @c Descriptor[] array into tables, rows, lines and columns and + * iterates over these components. + * + * The top-level organizational unit is the @e table. + * A table begins at a Descriptor with @c help!=NULL and extends up to + * a Descriptor with @c help==NULL. + * + * A table consists of @e rows. Due to line-wrapping and explicit breaks + * a row may take multiple lines on screen. Rows within the table are separated + * by \\n. They never cross Descriptor boundaries. This means a row ends either + * at \\n or the 0 at the end of the help string. + * + * A row consists of columns/cells. Columns/cells within a row are separated by \\t. + * Line breaks within a cell are marked by \\v. + * + * Rows in the same table need not have the same number of columns/cells. The + * extreme case are interjections, which are rows that contain neither \\t nor \\v. + * These are NOT treated specially by LinePartIterator, but they are treated + * specially by printUsage(). + * + * LinePartIterator iterates through the usage at 3 levels: table, row and part. + * Tables and rows are as described above. A @e part is a line within a cell. + * LinePartIterator iterates through 1st parts of all cells, then through the 2nd + * parts of all cells (if any),... @n + * Example: The row "1 \v 3 \t 2 \v 4" has 2 cells/columns and 4 parts. + * The parts will be returned in the order 1, 2, 3, 4. + * + * It is possible that some cells have fewer parts than others. In this case + * LinePartIterator will "fill up" these cells with 0-length parts. IOW, LinePartIterator + * always returns the same number of parts for each column. Note that this is different + * from the way rows and columns are handled. LinePartIterator does @e not guarantee that + * the same number of columns will be returned for each row. * - * See @ref LinePartIterator for details about the iteration. */ - bool next() + class LinePartIterator { - if (ptr == 0) - return false; - - if (col == -1) - { - col = 0; - update_length(); - return true; - } - - ptr += len; - while (true) - { - switch (*ptr) + const Descriptor* tablestart; //!< The 1st descriptor of the current table. + const Descriptor* rowdesc; //!< The Descriptor that contains the current row. + const char* rowstart; //!< Ptr to 1st character of current row within rowdesc->help. + const char* ptr; //!< Ptr to current part within the current row. + int col; //!< Index of current column. + int len; //!< Length of the current part (that ptr points at) in BYTES + int screenlen; //!< Length of the current part in screen columns (taking narrow/wide chars into account). + int max_line_in_block; //!< Greatest index of a line within the block. This is the number of \\v within the cell with the most \\vs. + int line_in_block; //!< Line index within the current cell of the current part. + int target_line_in_block; //!< Line index of the parts we should return to the user on this iteration. + bool hit_target_line; //!< Flag whether we encountered a part with line index target_line_in_block in the current cell. + + /** + * @brief Determines the byte and character lengths of the part at @ref ptr and + * stores them in @ref len and @ref screenlen respectively. + */ + void update_length() { - case '\v': - upmax(max_line_in_block, ++line_in_block); - ++ptr; - break; - case '\t': - if (!hit_target_line) // if previous column did not have the targetline - { // then "insert" a 0-length part - update_length(); - hit_target_line = true; - return true; + screenlen = 0; + for (len = 0; ptr[len] != 0 && ptr[len] != '\v' && ptr[len] != '\t' && ptr[len] != '\n'; ++len) + { + ++screenlen; + unsigned ch = (unsigned char) ptr[len]; + if (ch > 0xC1) // everything <= 0xC1 (yes, even 0xC1 itself) is not a valid UTF-8 start byte + { + // int __builtin_clz (unsigned int x) + // Returns the number of leading 0-bits in x, starting at the most significant bit + unsigned mask = (unsigned) -1 >> __builtin_clz(ch ^ 0xff); + ch = ch & mask; // mask out length bits, we don't verify their correctness + while (((unsigned char) ptr[len + 1] ^ 0x80) <= 0x3F) // while next byte is continuation byte + { + ch = (ch << 6) ^ (unsigned char) ptr[len + 1] ^ 0x80; // add continuation to char code + ++len; + } + // ch is the decoded unicode code point + if (ch >= 0x1100 && isWideChar(ch)) // the test for 0x1100 is here to avoid the function call in the Latin case + { + ++screenlen; + } + } } + } - hit_target_line = false; - line_in_block = 0; - ++col; - ++ptr; - break; - case 0: - case '\n': - if (!hit_target_line) // if previous column did not have the targetline - { // then "insert" a 0-length part - update_length(); - hit_target_line = true; - return true; + public: + + //! @brief Creates an iterator for @c usage. + LinePartIterator( + const Descriptor usage[]) + : tablestart(usage) + , rowdesc(0) + , rowstart(0) + , ptr(0) + , col(-1) + , len(0) + , max_line_in_block(0) + , line_in_block(0) + , target_line_in_block(0) + , hit_target_line(true) + { + } + + /** + * @brief Moves iteration to the next table (if any). Has to be called once on a new + * LinePartIterator to move to the 1st table. + * @retval false if moving to next table failed because no further table exists. + */ + bool nextTable() + { + // If this is NOT the first time nextTable() is called after the constructor, + // then skip to the next table break (i.e. a Descriptor with help == 0) + if (rowdesc != 0) + { + while (tablestart->help != 0 && tablestart->shortopt != 0) + { + ++tablestart; + } } - if (++target_line_in_block > max_line_in_block) + // Find the next table after the break (if any) + while (tablestart->help == 0 && tablestart->shortopt != 0) { - update_length(); - return false; + ++tablestart; } - hit_target_line = false; - line_in_block = 0; - col = 0; - ptr = rowstart; - continue; - default: - ++ptr; - continue; - } // switch + restartTable(); + return rowstart != 0; + } - if (line_in_block == target_line_in_block) + /** + * @brief Reset iteration to the beginning of the current table. + */ + void restartTable() { - update_length(); - hit_target_line = true; - return true; + rowdesc = tablestart; + rowstart = tablestart->help; + ptr = 0; } - } // while - } - - /** - * @brief Returns the index (counting from 0) of the column in which - * the part pointed to by @ref data() is located. - */ - int column() - { - return col; - } - /** - * @brief Returns the index (counting from 0) of the line within the current column - * this part belongs to. - */ - int line() - { - return target_line_in_block; // NOT line_in_block !!! It would be wrong if !hit_target_line - } + /** + * @brief Moves iteration to the next row (if any). Has to be called once after each call to + * @ref nextTable() to move to the 1st row of the table. + * @retval false if moving to next row failed because no further row exists. + */ + bool nextRow() + { + if (ptr == 0) + { + restartRow(); + return rowstart != 0; + } - /** - * @brief Returns the length of the part pointed to by @ref data() in raw chars (not UTF-8 characters). - */ - int length() - { - return len; - } + while (*ptr != 0 && *ptr != '\n') + { + ++ptr; + } - /** - * @brief Returns the width in screen columns of the part pointed to by @ref data(). - * Takes multi-byte UTF-8 sequences and wide characters into account. - */ - int screenLength() - { - return screenlen; - } + if (*ptr == 0) + { + if ((rowdesc + 1)->help == 0) // table break + { + return false; + } - /** - * @brief Returns the current part of the iteration. - */ - const char* data() - { - return ptr; - } - }; - - /** - * @internal - * @brief Takes input and line wraps it, writing out one line at a time so that - * it can be interleaved with output from other columns. - * - * The LineWrapper is used to handle the last column of each table as well as interjections. - * The LineWrapper is called once for each line of output. If the data given to it fits - * into the designated width of the last column it is simply written out. If there - * is too much data, an appropriate split point is located and only the data up to this - * split point is written out. The rest of the data is queued for the next line. - * That way the last column can be line wrapped and interleaved with data from - * other columns. The following example makes this clearer: - * @code - * Column 1,1 Column 2,1 This is a long text - * Column 1,2 Column 2,2 that does not fit into - * a single line. - * @endcode - * - * The difficulty in producing this output is that the whole string - * "This is a long text that does not fit into a single line" is the - * 1st and only part of column 3. In order to produce the above - * output the string must be output piecemeal, interleaved with - * the data from the other columns. - */ - class LineWrapper - { - static const int bufmask = 15; //!< Must be a power of 2 minus 1. - /** - * @brief Ring buffer for length component of pair (data, length). - */ - int lenbuf[bufmask + 1]; - /** - * @brief Ring buffer for data component of pair (data, length). - */ - const char* datbuf[bufmask + 1]; - /** - * @brief The indentation of the column to which the LineBuffer outputs. LineBuffer - * assumes that the indentation has already been written when @ref process() - * is called, so this value is only used when a buffer flush requires writing - * additional lines of output. - */ - int x; - /** - * @brief The width of the column to line wrap. - */ - int width; - int head; //!< @brief index for next write - int tail; //!< @brief index for next read - 1 (i.e. increment tail BEFORE read) + ++rowdesc; + rowstart = rowdesc->help; + } + else // if (*ptr == '\n') + { + rowstart = ptr + 1; + } - /** - * @brief Multiple methods of LineWrapper may decide to flush part of the buffer to - * free up space. The contract of process() says that only 1 line is output. So - * this variable is used to track whether something has output a line. It is - * reset at the beginning of process() and checked at the end to decide if - * output has already occurred or is still needed. - */ - bool wrote_something; + restartRow(); + return true; + } - bool buf_empty() - { - return ((tail + 1) & bufmask) == head; - } + /** + * @brief Reset iteration to the beginning of the current row. + */ + void restartRow() + { + ptr = rowstart; + col = -1; + len = 0; + screenlen = 0; + max_line_in_block = 0; + line_in_block = 0; + target_line_in_block = 0; + hit_target_line = true; + } - bool buf_full() - { - return tail == head; - } + /** + * @brief Moves iteration to the next part (if any). Has to be called once after each call to + * @ref nextRow() to move to the 1st part of the row. + * @retval false if moving to next part failed because no further part exists. + * + * See @ref LinePartIterator for details about the iteration. + */ + bool next() + { + if (ptr == 0) + { + return false; + } - void buf_store(const char* data, int len) - { - lenbuf[head] = len; - datbuf[head] = data; - head = (head + 1) & bufmask; - } + if (col == -1) + { + col = 0; + update_length(); + return true; + } - //! @brief Call BEFORE reading ...buf[tail]. - void buf_next() - { - tail = (tail + 1) & bufmask; - } + ptr += len; + while (true) + { + switch (*ptr) + { + case '\v': + upmax(max_line_in_block, ++line_in_block); + ++ptr; + break; + case '\t': + if (!hit_target_line) // if previous column did not have the targetline + { + // then "insert" a 0-length part + update_length(); + hit_target_line = true; + return true; + } + + hit_target_line = false; + line_in_block = 0; + ++col; + ++ptr; + break; + case 0: + case '\n': + if (!hit_target_line) // if previous column did not have the targetline + { + // then "insert" a 0-length part + update_length(); + hit_target_line = true; + return true; + } + + if (++target_line_in_block > max_line_in_block) + { + update_length(); + return false; + } + + hit_target_line = false; + line_in_block = 0; + col = 0; + ptr = rowstart; + continue; + default: + ++ptr; + continue; + } // switch + + if (line_in_block == target_line_in_block) + { + update_length(); + hit_target_line = true; + return true; + } + } // while + } - /** - * @brief Writes (data,len) into the ring buffer. If the buffer is full, a single line - * is flushed out of the buffer into @c write. - */ - void output(IStringWriter& write, const char* data, int len) - { - if (buf_full()) - write_one_line(write); + /** + * @brief Returns the index (counting from 0) of the column in which + * the part pointed to by @ref data() is located. + */ + int column() + { + return col; + } - buf_store(data, len); - } + /** + * @brief Returns the index (counting from 0) of the line within the current column + * this part belongs to. + */ + int line() + { + return target_line_in_block; // NOT line_in_block !!! It would be wrong if !hit_target_line + } - /** - * @brief Writes a single line of output from the buffer to @c write. - */ - void write_one_line(IStringWriter& write) - { - if (wrote_something) // if we already wrote something, we need to start a new line - { - write("\n", 1); - int _ = 0; - indent(write, _, x); - } + /** + * @brief Returns the length of the part pointed to by @ref data() in raw chars (not UTF-8 characters). + */ + int length() + { + return len; + } - if (!buf_empty()) - { - buf_next(); - write(datbuf[tail], lenbuf[tail]); - } + /** + * @brief Returns the width in screen columns of the part pointed to by @ref data(). + * Takes multi-byte UTF-8 sequences and wide characters into account. + */ + int screenLength() + { + return screenlen; + } - wrote_something = true; - } - public: + /** + * @brief Returns the current part of the iteration. + */ + const char* data() + { + return ptr; + } - /** - * @brief Writes out all remaining data from the LineWrapper using @c write. - * Unlike @ref process() this method indents all lines including the first and - * will output a \\n at the end (but only if something has been written). - */ - void flush(IStringWriter& write) - { - if (buf_empty()) - return; - int _ = 0; - indent(write, _, x); - wrote_something = false; - while (!buf_empty()) - write_one_line(write); - write("\n", 1); - } + }; /** - * @brief Process, wrap and output the next piece of data. + * @internal + * @brief Takes input and line wraps it, writing out one line at a time so that + * it can be interleaved with output from other columns. * - * process() will output at least one line of output. This is not necessarily - * the @c data passed in. It may be data queued from a prior call to process(). - * If the internal buffer is full, more than 1 line will be output. + * The LineWrapper is used to handle the last column of each table as well as interjections. + * The LineWrapper is called once for each line of output. If the data given to it fits + * into the designated width of the last column it is simply written out. If there + * is too much data, an appropriate split point is located and only the data up to this + * split point is written out. The rest of the data is queued for the next line. + * That way the last column can be line wrapped and interleaved with data from + * other columns. The following example makes this clearer: + * @code + * Column 1,1 Column 2,1 This is a long text + * Column 1,2 Column 2,2 that does not fit into + * a single line. + * @endcode * - * process() assumes that the a proper amount of indentation has already been - * output. It won't write any further indentation before the 1st line. If - * more than 1 line is written due to buffer constraints, the lines following - * the first will be indented by this method, though. - * - * No \\n is written by this method after the last line that is written. - * - * @param write where to write the data. - * @param data the new chunk of data to write. - * @param len the length of the chunk of data to write. + * The difficulty in producing this output is that the whole string + * "This is a long text that does not fit into a single line" is the + * 1st and only part of column 3. In order to produce the above + * output the string must be output piecemeal, interleaved with + * the data from the other columns. */ - void process(IStringWriter& write, const char* data, int len) + class LineWrapper { - wrote_something = false; + static const int bufmask = 15; //!< Must be a power of 2 minus 1. + /** + * @brief Ring buffer for length component of pair (data, length). + */ + int lenbuf[bufmask + 1]; + /** + * @brief Ring buffer for data component of pair (data, length). + */ + const char* datbuf[bufmask + 1]; + /** + * @brief The indentation of the column to which the LineBuffer outputs. LineBuffer + * assumes that the indentation has already been written when @ref process() + * is called, so this value is only used when a buffer flush requires writing + * additional lines of output. + */ + int x; + /** + * @brief The width of the column to line wrap. + */ + int width; + int head; //!< @brief index for next write + int tail; //!< @brief index for next read - 1 (i.e. increment tail BEFORE read) + + /** + * @brief Multiple methods of LineWrapper may decide to flush part of the buffer to + * free up space. The contract of process() says that only 1 line is output. So + * this variable is used to track whether something has output a line. It is + * reset at the beginning of process() and checked at the end to decide if + * output has already occurred or is still needed. + */ + bool wrote_something; + + bool buf_empty() + { + return ((tail + 1) & bufmask) == head; + } + + bool buf_full() + { + return tail == head; + } + + void buf_store( + const char* data, + int len) + { + lenbuf[head] = len; + datbuf[head] = data; + head = (head + 1) & bufmask; + } - while (len > 0) - { - if (len <= width) // quick test that works because utf8width <= len (all wide chars have at least 2 bytes) + //! @brief Call BEFORE reading ...buf[tail]. + void buf_next() { - output(write, data, len); - len = 0; + tail = (tail + 1) & bufmask; } - else // if (len > width) it's possible (but not guaranteed) that utf8len > width + + /** + * @brief Writes (data,len) into the ring buffer. If the buffer is full, a single line + * is flushed out of the buffer into @c write. + */ + void output( + IStringWriter& write, + const char* data, + int len) { - int utf8width = 0; - int maxi = 0; - while (maxi < len && utf8width < width) - { - int charbytes = 1; - unsigned ch = (unsigned char) data[maxi]; - if (ch > 0xC1) // everything <= 0xC1 (yes, even 0xC1 itself) is not a valid UTF-8 start byte + if (buf_full()) { - // int __builtin_clz (unsigned int x) - // Returns the number of leading 0-bits in x, starting at the most significant bit - unsigned mask = (unsigned) -1 >> __builtin_clz(ch ^ 0xff); - ch = ch & mask; // mask out length bits, we don't verify their correctness - while ((maxi + charbytes < len) && // - (((unsigned char) data[maxi + charbytes] ^ 0x80) <= 0x3F)) // while next byte is continuation byte - { - ch = (ch << 6) ^ (unsigned char) data[maxi + charbytes] ^ 0x80; // add continuation to char code - ++charbytes; - } - // ch is the decoded unicode code point - if (ch >= 0x1100 && isWideChar(ch)) // the test for 0x1100 is here to avoid the function call in the Latin case - { - if (utf8width + 2 > width) - break; - ++utf8width; - } + write_one_line(write); } - ++utf8width; - maxi += charbytes; - } - // data[maxi-1] is the last byte of the UTF-8 sequence of the last character that fits - // onto the 1st line. If maxi == len, all characters fit on the line. + buf_store(data, len); + } - if (maxi == len) - { - output(write, data, len); - len = 0; - } - else // if (maxi < len) at least 1 character (data[maxi] that is) doesn't fit on the line - { - int i; - for (i = maxi; i >= 0; --i) - if (data[i] == ' ') - break; + /** + * @brief Writes a single line of output from the buffer to @c write. + */ + void write_one_line( + IStringWriter& write) + { + if (wrote_something) // if we already wrote something, we need to start a new line + { + write("\n", 1); + int _ = 0; + indent(write, _, x); + } + + if (!buf_empty()) + { + buf_next(); + write(datbuf[tail], lenbuf[tail]); + } - if (i >= 0) + wrote_something = true; + } + + public: + + /** + * @brief Writes out all remaining data from the LineWrapper using @c write. + * Unlike @ref process() this method indents all lines including the first and + * will output a \\n at the end (but only if something has been written). + */ + void flush( + IStringWriter& write) + { + if (buf_empty()) { - output(write, data, i); - data += i + 1; - len -= i + 1; + return; } - else // did not find a space to split at => split before data[maxi] - { // data[maxi] is always the beginning of a character, never a continuation byte - output(write, data, maxi); - data += maxi; - len -= maxi; + int _ = 0; + indent(write, _, x); + wrote_something = false; + while (!buf_empty()) + { + write_one_line(write); } - } + write("\n", 1); } - } - if (!wrote_something) // if we didn't already write something to make space in the buffer - write_one_line(write); // write at most one line of actual output - } - /** - * @brief Constructs a LineWrapper that wraps its output to fit into - * screen columns @c x1 (incl.) to @c x2 (excl.). - * - * @c x1 gives the indentation LineWrapper uses if it needs to indent. - */ - LineWrapper(int x1, int x2) : - x(x1), width(x2 - x1), head(0), tail(bufmask) - { - if (width < 2) // because of wide characters we need at least width 2 or the code breaks - width = 2; - } - }; - - /** - * @internal - * @brief This is the implementation that is shared between all printUsage() templates. - * Because all printUsage() templates share this implementation, there is no template bloat. - */ - static void printUsage(IStringWriter& write, const Descriptor usage[], int width = 80, // - int last_column_min_percent = 50, int last_column_own_line_max_percent = 75) - { - if (width < 1) // protect against nonsense values - width = 80; - - if (width > 10000) // protect against overflow in the following computation - width = 10000; - - int last_column_min_width = ((width * last_column_min_percent) + 50) / 100; - int last_column_own_line_max_width = ((width * last_column_own_line_max_percent) + 50) / 100; - if (last_column_own_line_max_width == 0) - last_column_own_line_max_width = 1; - - LinePartIterator part(usage); - while (part.nextTable()) - { - - /***************** Determine column widths *******************************/ - - const int maxcolumns = 8; // 8 columns are enough for everyone - int col_width[maxcolumns]; - int lastcolumn; - int leftwidth; - int overlong_column_threshold = 10000; - do - { - lastcolumn = 0; - for (int i = 0; i < maxcolumns; ++i) - col_width[i] = 0; - - part.restartTable(); - while (part.nextRow()) + /** + * @brief Process, wrap and output the next piece of data. + * + * process() will output at least one line of output. This is not necessarily + * the @c data passed in. It may be data queued from a prior call to process(). + * If the internal buffer is full, more than 1 line will be output. + * + * process() assumes that the a proper amount of indentation has already been + * output. It won't write any further indentation before the 1st line. If + * more than 1 line is written due to buffer constraints, the lines following + * the first will be indented by this method, though. + * + * No \\n is written by this method after the last line that is written. + * + * @param write where to write the data. + * @param data the new chunk of data to write. + * @param len the length of the chunk of data to write. + */ + void process( + IStringWriter& write, + const char* data, + int len) + { + wrote_something = false; + + while (len > 0) + { + if (len <= width) // quick test that works because utf8width <= len (all wide chars have at least 2 bytes) + { + output(write, data, len); + len = 0; + } + else // if (len > width) it's possible (but not guaranteed) that utf8len > width + { + int utf8width = 0; + int maxi = 0; + while (maxi < len && utf8width < width) + { + int charbytes = 1; + unsigned ch = (unsigned char) data[maxi]; + if (ch > 0xC1) // everything <= 0xC1 (yes, even 0xC1 itself) is not a valid UTF-8 start byte + { + // int __builtin_clz (unsigned int x) + // Returns the number of leading 0-bits in x, starting at the most significant bit + unsigned mask = (unsigned) -1 >> __builtin_clz(ch ^ 0xff); + ch = ch & mask; // mask out length bits, we don't verify their correctness + while ((maxi + charbytes < len) && // + (((unsigned char) data[maxi + charbytes] ^ 0x80) <= 0x3F)) // while next byte is continuation byte + { + ch = (ch << 6) ^ (unsigned char) data[maxi + charbytes] ^ 0x80; // add continuation to char code + ++charbytes; + } + // ch is the decoded unicode code point + if (ch >= 0x1100 && isWideChar(ch)) // the test for 0x1100 is here to avoid the function call in the Latin case + { + if (utf8width + 2 > width) + { + break; + } + ++utf8width; + } + } + ++utf8width; + maxi += charbytes; + } + + // data[maxi-1] is the last byte of the UTF-8 sequence of the last character that fits + // onto the 1st line. If maxi == len, all characters fit on the line. + + if (maxi == len) + { + output(write, data, len); + len = 0; + } + else // if (maxi < len) at least 1 character (data[maxi] that is) doesn't fit on the line + { + int i; + for (i = maxi; i >= 0; --i) + { + if (data[i] == ' ') + { + break; + } + } + + if (i >= 0) + { + output(write, data, i); + data += i + 1; + len -= i + 1; + } + else // did not find a space to split at => split before data[maxi] + { + // data[maxi] is always the beginning of a character, never a continuation byte + output(write, data, maxi); + data += maxi; + len -= maxi; + } + } + } + } + if (!wrote_something) // if we didn't already write something to make space in the buffer + { + write_one_line(write); // write at most one line of actual output + } + } + + /** + * @brief Constructs a LineWrapper that wraps its output to fit into + * screen columns @c x1 (incl.) to @c x2 (excl.). + * + * @c x1 gives the indentation LineWrapper uses if it needs to indent. + */ + LineWrapper( + int x1, + int x2) + : x(x1) + , width(x2 - x1) + , head(0) + , tail(bufmask) { - while (part.next()) - { - if (part.column() < maxcolumns) + if (width < 2) // because of wide characters we need at least width 2 or the code breaks { - upmax(lastcolumn, part.column()); - if (part.screenLength() < overlong_column_threshold) - // We don't let rows that don't use table separators (\t or \v) influence - // the width of column 0. This allows the user to interject section headers - // or explanatory paragraphs that do not participate in the table layout. - if (part.column() > 0 || part.line() > 0 || part.data()[part.length()] == '\t' - || part.data()[part.length()] == '\v') - upmax(col_width[part.column()], part.screenLength()); + width = 2; } - } } - /* - * If the last column doesn't fit on the same - * line as the other columns, we can fix that by starting it on its own line. - * However we can't do this for any of the columns 0..lastcolumn-1. - * If their sum exceeds the maximum width we try to fix this by iteratively - * ignoring the widest line parts in the width determination until - * we arrive at a series of column widths that fit into one line. - * The result is a layout where everything is nicely formatted - * except for a few overlong fragments. - * */ - - leftwidth = 0; - overlong_column_threshold = 0; - for (int i = 0; i < lastcolumn; ++i) + }; + + /** + * @internal + * @brief This is the implementation that is shared between all printUsage() templates. + * Because all printUsage() templates share this implementation, there is no template bloat. + */ + static void printUsage( + IStringWriter& write, + const Descriptor usage[], + int width = 80, // + int last_column_min_percent = 50, + int last_column_own_line_max_percent = 75) + { + if (width < 1) // protect against nonsense values + { + width = 80; + } + + if (width > 10000) // protect against overflow in the following computation { - leftwidth += col_width[i]; - upmax(overlong_column_threshold, col_width[i]); + width = 10000; } - } while (leftwidth > width); - - /**************** Determine tab stops and last column handling **********************/ - - int tabstop[maxcolumns]; - tabstop[0] = 0; - for (int i = 1; i < maxcolumns; ++i) - tabstop[i] = tabstop[i - 1] + col_width[i - 1]; - - int rightwidth = width - tabstop[lastcolumn]; - bool print_last_column_on_own_line = false; - if (rightwidth < last_column_min_width && // if we don't have the minimum requested width for the last column - ( col_width[lastcolumn] == 0 || // and all last columns are > overlong_column_threshold - rightwidth < col_width[lastcolumn] // or there is at least one last column that requires more than the space available - ) - ) - { - print_last_column_on_own_line = true; - rightwidth = last_column_own_line_max_width; - } - - // If lastcolumn == 0 we must disable print_last_column_on_own_line because - // otherwise 2 copies of the last (and only) column would be output. - // Actually this is just defensive programming. It is currently not - // possible that lastcolumn==0 and print_last_column_on_own_line==true - // at the same time, because lastcolumn==0 => tabstop[lastcolumn] == 0 => - // rightwidth==width => rightwidth>=last_column_min_width (unless someone passes - // a bullshit value >100 for last_column_min_percent) => the above if condition - // is false => print_last_column_on_own_line==false - if (lastcolumn == 0) - print_last_column_on_own_line = false; - - LineWrapper lastColumnLineWrapper(width - rightwidth, width); - LineWrapper interjectionLineWrapper(0, width); - - part.restartTable(); - - /***************** Print out all rows of the table *************************************/ - - while (part.nextRow()) - { - int x = -1; - while (part.next()) + int last_column_min_width = ((width * last_column_min_percent) + 50) / 100; + int last_column_own_line_max_width = ((width * last_column_own_line_max_percent) + 50) / 100; + if (last_column_own_line_max_width == 0) { - if (part.column() > lastcolumn) - continue; // drop excess columns (can happen if lastcolumn == maxcolumns-1) - - if (part.column() == 0) - { - if (x >= 0) - write("\n", 1); - x = 0; - } - - indent(write, x, tabstop[part.column()]); - - if ((part.column() < lastcolumn) - && (part.column() > 0 || part.line() > 0 || part.data()[part.length()] == '\t' - || part.data()[part.length()] == '\v')) - { - write(part.data(), part.length()); - x += part.screenLength(); - } - else // either part.column() == lastcolumn or we are in the special case of - // an interjection that doesn't contain \v or \t - { - // NOTE: This code block is not necessarily executed for - // each line, because some rows may have fewer columns. - - LineWrapper& lineWrapper = (part.column() == 0) ? interjectionLineWrapper : lastColumnLineWrapper; - - if (!print_last_column_on_own_line || part.column() != lastcolumn) - lineWrapper.process(write, part.data(), part.length()); - } - } // while - - if (print_last_column_on_own_line) + last_column_own_line_max_width = 1; + } + + LinePartIterator part(usage); + while (part.nextTable()) { - part.restartRow(); - while (part.next()) - { - if (part.column() == lastcolumn) + + /***************** Determine column widths *******************************/ + + const int maxcolumns = 8; // 8 columns are enough for everyone + int col_width[maxcolumns]; + int lastcolumn; + int leftwidth; + int overlong_column_threshold = 10000; + do + { + lastcolumn = 0; + for (int i = 0; i < maxcolumns; ++i) + { + col_width[i] = 0; + } + + part.restartTable(); + while (part.nextRow()) + { + while (part.next()) + { + if (part.column() < maxcolumns) + { + upmax(lastcolumn, part.column()); + if (part.screenLength() < overlong_column_threshold) + { + // We don't let rows that don't use table separators (\t or \v) influence + // the width of column 0. This allows the user to interject section headers + // or explanatory paragraphs that do not participate in the table layout. + if (part.column() > 0 || part.line() > 0 || part.data()[part.length()] == '\t' + || part.data()[part.length()] == '\v') + { + upmax(col_width[part.column()], part.screenLength()); + } + } + } + } + } + + /* + * If the last column doesn't fit on the same + * line as the other columns, we can fix that by starting it on its own line. + * However we can't do this for any of the columns 0..lastcolumn-1. + * If their sum exceeds the maximum width we try to fix this by iteratively + * ignoring the widest line parts in the width determination until + * we arrive at a series of column widths that fit into one line. + * The result is a layout where everything is nicely formatted + * except for a few overlong fragments. + * */ + + leftwidth = 0; + overlong_column_threshold = 0; + for (int i = 0; i < lastcolumn; ++i) + { + leftwidth += col_width[i]; + upmax(overlong_column_threshold, col_width[i]); + } + + } while (leftwidth > width); + + /**************** Determine tab stops and last column handling **********************/ + + int tabstop[maxcolumns]; + tabstop[0] = 0; + for (int i = 1; i < maxcolumns; ++i) { - write("\n", 1); - int _ = 0; - indent(write, _, width - rightwidth); - lastColumnLineWrapper.process(write, part.data(), part.length()); + tabstop[i] = tabstop[i - 1] + col_width[i - 1]; } - } - } - write("\n", 1); - lastColumnLineWrapper.flush(write); - interjectionLineWrapper.flush(write); - } + int rightwidth = width - tabstop[lastcolumn]; + bool print_last_column_on_own_line = false; + if (rightwidth < last_column_min_width && // if we don't have the minimum requested width for the last column + ( col_width[lastcolumn] == 0 || // and all last columns are > overlong_column_threshold + rightwidth < col_width[lastcolumn] // or there is at least one last column that requires more than the space available + ) + ) + { + print_last_column_on_own_line = true; + rightwidth = last_column_own_line_max_width; + } + + // If lastcolumn == 0 we must disable print_last_column_on_own_line because + // otherwise 2 copies of the last (and only) column would be output. + // Actually this is just defensive programming. It is currently not + // possible that lastcolumn==0 and print_last_column_on_own_line==true + // at the same time, because lastcolumn==0 => tabstop[lastcolumn] == 0 => + // rightwidth==width => rightwidth>=last_column_min_width (unless someone passes + // a bullshit value >100 for last_column_min_percent) => the above if condition + // is false => print_last_column_on_own_line==false + if (lastcolumn == 0) + { + print_last_column_on_own_line = false; + } + + LineWrapper lastColumnLineWrapper(width - rightwidth, width); + LineWrapper interjectionLineWrapper(0, width); + + part.restartTable(); + + /***************** Print out all rows of the table *************************************/ + + while (part.nextRow()) + { + int x = -1; + while (part.next()) + { + if (part.column() > lastcolumn) + { + continue; // drop excess columns (can happen if lastcolumn == maxcolumns-1) + + } + if (part.column() == 0) + { + if (x >= 0) + { + write("\n", 1); + } + x = 0; + } + + indent(write, x, tabstop[part.column()]); + + if ((part.column() < lastcolumn) + && (part.column() > 0 || part.line() > 0 || part.data()[part.length()] == '\t' + || part.data()[part.length()] == '\v')) + { + write(part.data(), part.length()); + x += part.screenLength(); + } + else // either part.column() == lastcolumn or we are in the special case of + // an interjection that doesn't contain \v or \t + { + // NOTE: This code block is not necessarily executed for + // each line, because some rows may have fewer columns. + + LineWrapper& lineWrapper = + (part.column() == 0) ? interjectionLineWrapper : lastColumnLineWrapper; + + if (!print_last_column_on_own_line || part.column() != lastcolumn) + { + lineWrapper.process(write, part.data(), part.length()); + } + } + } // while + + if (print_last_column_on_own_line) + { + part.restartRow(); + while (part.next()) + { + if (part.column() == lastcolumn) + { + write("\n", 1); + int _ = 0; + indent(write, _, width - rightwidth); + lastColumnLineWrapper.process(write, part.data(), part.length()); + } + } + } + + write("\n", 1); + lastColumnLineWrapper.flush(write); + interjectionLineWrapper.flush(write); + } + } } - } } ; @@ -2775,47 +3181,74 @@ struct PrintUsageImplementation * @endcode */ template -void printUsage(OStream& prn, const Descriptor usage[], int width = 80, int last_column_min_percent = 50, - int last_column_own_line_max_percent = 75) +void printUsage( + OStream& prn, + const Descriptor usage[], + int width = 80, + int last_column_min_percent = 50, + int last_column_own_line_max_percent = 75) { - PrintUsageImplementation::OStreamWriter write(prn); - PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, last_column_own_line_max_percent); + PrintUsageImplementation::OStreamWriter write(prn); + PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, + last_column_own_line_max_percent); } template -void printUsage(Function* prn, const Descriptor usage[], int width = 80, int last_column_min_percent = 50, - int last_column_own_line_max_percent = 75) +void printUsage( + Function* prn, + const Descriptor usage[], + int width = 80, + int last_column_min_percent = 50, + int last_column_own_line_max_percent = 75) { - PrintUsageImplementation::FunctionWriter write(prn); - PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, last_column_own_line_max_percent); + PrintUsageImplementation::FunctionWriter write(prn); + PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, + last_column_own_line_max_percent); } template -void printUsage(const Temporary& prn, const Descriptor usage[], int width = 80, int last_column_min_percent = 50, - int last_column_own_line_max_percent = 75) +void printUsage( + const Temporary& prn, + const Descriptor usage[], + int width = 80, + int last_column_min_percent = 50, + int last_column_own_line_max_percent = 75) { - PrintUsageImplementation::TemporaryWriter write(prn); - PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, last_column_own_line_max_percent); + PrintUsageImplementation::TemporaryWriter write(prn); + PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, + last_column_own_line_max_percent); } template -void printUsage(Syscall* prn, int fd, const Descriptor usage[], int width = 80, int last_column_min_percent = 50, - int last_column_own_line_max_percent = 75) +void printUsage( + Syscall* prn, + int fd, + const Descriptor usage[], + int width = 80, + int last_column_min_percent = 50, + int last_column_own_line_max_percent = 75) { - PrintUsageImplementation::SyscallWriter write(prn, fd); - PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, last_column_own_line_max_percent); + PrintUsageImplementation::SyscallWriter write(prn, fd); + PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, + last_column_own_line_max_percent); } template -void printUsage(Function* prn, Stream* stream, const Descriptor usage[], int width = 80, int last_column_min_percent = - 50, - int last_column_own_line_max_percent = 75) +void printUsage( + Function* prn, + Stream* stream, + const Descriptor usage[], + int width = 80, + int last_column_min_percent = + 50, + int last_column_own_line_max_percent = 75) { - PrintUsageImplementation::StreamWriter write(prn, stream); - PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, last_column_own_line_max_percent); + PrintUsageImplementation::StreamWriter write(prn, stream); + PrintUsageImplementation::printUsage(write, usage, width, last_column_min_percent, + last_column_own_line_max_percent); } -} +} // namespace option // namespace option -#endif /* OPTIONPARSER_H_ */ \ No newline at end of file +#endif /* OPTIONPARSER_H_ */ diff --git a/readthedocs.yaml b/readthedocs.yaml index 48f3a5fbf..d462c5988 100644 --- a/readthedocs.yaml +++ b/readthedocs.yaml @@ -5,15 +5,19 @@ # Required version: 2 -# Set the OS, Python version and other tools you might need +# Build documentation in the docs/ directory with Sphinx +sphinx: + configuration: docs/conf.py + +# Set OS specifically for version compatibility build: os: ubuntu-22.04 tools: - python: "3.7" + python: "3.11" -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py +# Build documentation with MkDocs +#mkdocs: +# configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF and ePub formats: all