diff --git a/.clang-format b/.clang-format index bbcb3665..61c2e565 100644 --- a/.clang-format +++ b/.clang-format @@ -3,8 +3,8 @@ BasedOnStyle: Google # Align assignments and similar statements -AlignConsecutiveAssignments: true -AlignConsecutiveDeclarations: true +AlignConsecutiveAssignments: Consecutive +AlignConsecutiveDeclarations: Consecutive # Align the * in declarations DerivePointerAlignment: false @@ -23,7 +23,7 @@ BreakBeforeBraces: Custom BraceWrapping: AfterCaseLabel: true AfterClass: true - AfterControlStatement: true + AfterControlStatement: Always AfterEnum: true AfterFunction: true AfterNamespace: true @@ -32,6 +32,7 @@ BraceWrapping: BeforeCatch: true BeforeElse: true IndentBraces: false + SplitEmptyFunction: false # Control spaces around various constructs #SpacesInParens: Custom @@ -61,7 +62,7 @@ NamespaceIndentation: None ReflowComments: true # Additional settings to match Source SDK 2013 style -AllowShortIfStatementsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AllowShortFunctionsOnASingleLine: All diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml new file mode 100644 index 00000000..fce98321 --- /dev/null +++ b/.github/workflows/cmake.yml @@ -0,0 +1,220 @@ +name: CMake CI (Test customfetch) + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build-ubuntu_22-04: + runs-on: ubuntu-22.04 + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Install Packages + run: | + sudo apt-get purge firefox # Slows down the installation alot, fuck snap + sudo apt-get update && sudo apt upgrade -y + sudo apt-get install neofetch tree build-essential cmake g++-11 libwayland-dev gettext libdconf-dev libglib2.0-dev libarchive-tools -y + + - name: Clean + run: make distclean + + - name: Compile and install + run: | + export CXX=x86_64-linux-gnu-g++-11 + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr + make + sudo make install + + - name: Test neofetch + run: neofetch + + - name: Check system values + run: | + ls -l /sys/devices/virtual/dmi/id/ /sys/class/dmi/id/ + grep -Eri "virtual" /sys/class/dmi/id/ || true + cat /sys/devices/virtual/dmi/id/board_name /sys/devices/virtual/dmi/id/board_vendor /sys/devices/virtual/dmi/id/board_version + tree /sys/devices/system/cpu/cpu0/ + printf "/etc/os-release\n" && cat /etc/os-release + printf "getting 0x5353 hexcode\n" && grep -nri "5353" /sys/class/ || true + + - name: Test customfetch + run: customfetch --wrap-lines + + - name: Upload to github artifacts + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: customfetch + path: ./build/customfetch + + build-ubuntu-22_04-GUI: + runs-on: ubuntu-22.04 + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Install Packages + run: | + sudo apt-get purge firefox # Slows down the installation alot, fuck snap + sudo apt-get update && sudo apt upgrade -y + sudo apt-get install neofetch tree build-essential cmake g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev -y + + - name: Clean + run: make distclean + + - name: Compile and install + run: | + export CXX=x86_64-linux-gnu-g++-11 + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DGUI_APP=1 -DCMAKE_INSTALL_PREFIX=/usr + make + sudo make install + + - name: Test neofetch + run: neofetch + + - name: Test customfetch + run: customfetch-gui --version + + - name: Upload to github artifacts + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: customfetch-gui + path: ./build/customfetch-gui + + build_Arch: + runs-on: ubuntu-latest + permissions: + contents: read + container: + image: archlinux + + steps: + - uses: actions/checkout@v4 + + - name: Install the packages + run: pacman -Syyu git sudo binutils cmake gdb base-devel fakeroot pkgconf tree fastfetch --noconfirm --needed + + - name: get /etc/sudoers + run: | + sed -i "s#root ALL=(ALL:ALL) ALL#root ALL=(ALL:ALL) NOPASSWD: ALL\nnobody ALL=(ALL:ALL) NOPASSWD: ALL#g" /etc/sudoers + cat /etc/sudoers + - name: get /etc/makepkg.conf + run: | + sed -i "s#purge debug lto#purge !debug lto#" /etc/makepkg.conf + cat /etc/makepkg.conf + + - name: Clean + run: make distclean + + - name: Compile and install + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/usr + make + make install + + - name: Test fastfetch + run: fastfetch + + - name: Check system values + run: | + ls -l /sys/devices/virtual/dmi/id/ /sys/class/dmi/id/ + grep -Eri "virtual" /sys/class/dmi/id/ || true + cat /sys/devices/virtual/dmi/id/board_name /sys/devices/virtual/dmi/id/board_vendor /sys/devices/virtual/dmi/id/board_version + tree /sys/devices/system/cpu/cpu0/ + printf "/etc/os-release\n" && cat /etc/os-release + + - name: Test customfetch + run: customfetch --wrap-lines + + build-macos: + runs-on: macos-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Install the packages + run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite fastfetch neofetch pkg-config tree llvm@19 + + - name: uname -a && clang++ --version + run: uname -a && echo "\n" && $(brew --prefix llvm@19)/bin/clang++ --version + + - name: Clean + run: make distclean + + - name: Compile and install + run: | + export CXX=$(brew --prefix llvm@19)/bin/clang++ + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug + make + + - name: Test neofetch + run: neofetch + + - name: Test fastfetch + run: fastfetch + + - name: Test customfetch + run: ./build/customfetch -D assets + + - name: Upload to github artifacts + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: customfetch-macos + path: ./build/customfetch + + build-macos-GUI: + runs-on: macos-latest + permissions: + contents: read + + steps: + - uses: actions/checkout@v4 + + - name: Install the packages + run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite fastfetch neofetch tree pkg-config gtkmm3 llvm@19 + + - name: uname -a && clang++ --version + run: uname -a && echo "\n" && $(brew --prefix llvm@19)/bin/clang++ --version + + - name: Clean + run: make distclean + + - name: Compile and install + run: | + export CXX=$(brew --prefix llvm@19)/bin/clang++ + export PKG_CONFIG_PATH="$(brew --prefix)/lib/pkgconfig:$(brew --prefix)/opt/gtkmm3/lib/pkgconfig:$(brew --prefix)/opt/gtk+3/lib/pkgconfig" + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DGUI_APP=1 -DCMAKE_INSTALL_PREFIX=/usr + make + + - name: Test neofetch + run: neofetch + + - name: Test fastfetch + run: fastfetch + + - name: Test customfetch + run: ./build/customfetch-gui --version + + - name: Upload to github artifacts + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: customfetch-macos-gui + path: ./build/customfetch-gui diff --git a/.github/workflows/makefile.yml b/.github/workflows/makefile.yml index ab00f3cc..755b88ab 100644 --- a/.github/workflows/makefile.yml +++ b/.github/workflows/makefile.yml @@ -2,45 +2,46 @@ name: Makefile CI (Test customfetch) on: push: - branches: [ "main", "test", "android" ] + branches: [ "main", "plugins" ] pull_request: - branches: [ "main", "test", "android" ] + branches: [ "main", "plugins" ] jobs: build-deb: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 - name: create tmp directory - run: mkdir /tmp/customfetch-1.0.0.orig + run: mkdir /tmp/customfetch-2.0.0-beta1.orig - name: Install Packages run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get upgrade -y - sudo apt-get install libwayland-dev g++-10 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools + sudo apt-get purge firefox # Slows down the installation alot, fuck snap + sudo apt-get update && sudo apt-get upgrade -y + sudo apt-get install libwayland-dev g++-11 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools sudo apt-get install binutils gettext lintian debhelper devscripts debmake autoconf automake autotools-dev dh-make fakeroot xutils pbuilder -y - name: Clean - run: make clean + run: make distclean - name: Create deb run: | - cp -r $GITHUB_WORKSPACE /tmp/customfetch-1.0.0.orig - cd /tmp/customfetch-1.0.0.orig/customfetch + cp -r $GITHUB_WORKSPACE /tmp/customfetch-2.0.0-beta1.orig + cd /tmp/customfetch-2.0.0-beta1.orig/customfetch mkdir -p Debian/Debhelper/Buildsystem/ wget https://github.com/Debian/debhelper/raw/master/lib/Debian/Debhelper/Buildsystem/makefile.pm -O Debian/Debhelper/Buildsystem/make.pm sed -i "s#package Debian::Debhelper::Buildsystem::makefile#package Debian::Debhelper::Buildsystem::make#g" Debian/Debhelper/Buildsystem/make.pm - sed -i "s#DEBUG=0#DEBUG=0 CXX=x86_64-linux-gnu-g++-10#g" debian/rules + sed -i "s#DEBUG=0#DEBUG=0 CXX=x86_64-linux-gnu-g++-11#g" debian/rules dpkg-buildpackage -us -uc - name: Install test customfetch run: | - cd /tmp/customfetch-1.0.0.orig - sudo dpkg -i customfetch_1.0.0-1_amd64.deb + cd /tmp/customfetch-2.0.0-beta1.orig + sudo dpkg -i customfetch_2.0.0-beta1_amd64.deb customfetch --wrap-lines - name: Upload to github artifacts @@ -48,63 +49,27 @@ jobs: with: if-no-files-found: error name: customfetch_amd64 - path: /tmp/customfetch-1.0.0.orig/customfetch_1.0.0-1_amd64.deb + path: /tmp/customfetch-2.0.0-beta1.orig/customfetch_2.0.0-beta1_amd64.deb - build-deb-gui: - runs-on: ubuntu-20.04 + build-ubuntu_22-04: + runs-on: ubuntu-22.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 - - - name: create tmp directory - run: mkdir /tmp/customfetch-1.0.0.orig - name: Install Packages run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get upgrade -y - sudo apt-get install libwayland-dev g++-10 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools - sudo apt-get install binutils lintian debhelper gettext devscripts debmake autoconf automake autotools-dev dh-make fakeroot xutils pbuilder -y - - - name: Clean - run: make clean - - - name: Create deb - run: | - cp -r $GITHUB_WORKSPACE /tmp/customfetch-1.0.0.orig - cd /tmp/customfetch-1.0.0.orig/customfetch - mkdir -p Debian/Debhelper/Buildsystem/ - wget https://github.com/Debian/debhelper/raw/master/lib/Debian/Debhelper/Buildsystem/makefile.pm -O Debian/Debhelper/Buildsystem/make.pm - sed -i "s#package Debian::Debhelper::Buildsystem::makefile#package Debian::Debhelper::Buildsystem::make#g" Debian/Debhelper/Buildsystem/make.pm - sed -i "s#GUI_APP=0#GUI_APP=1 CXX=x86_64-linux-gnu-g++-10#g" debian/rules - sed -i "/^Build-Depends:/a Depends: libgtkmm-3.0-1v5,libgtk-3-0" debian/control - dpkg-buildpackage -us -uc - - - name: Upload to github artifacts - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - name: customfetch-gui_amd64 - path: /tmp/customfetch-1.0.0.orig/customfetch_1.0.0-1_amd64.deb - - build_ubuntu-20_04: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - - - name: Install Packages - run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get purge firefox # Slows down the installation alot, fuck snap sudo apt-get update && sudo apt upgrade -y - sudo apt-get install neofetch tree build-essential g++-10 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y + sudo apt-get install neofetch tree build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y - name: Clean - run: make clean + run: make distclean - name: Compile and install - run: make DEBUG=1 GUI_APP=0 CXX=x86_64-linux-gnu-g++-10 && sudo make install + run: make DEBUG=1 GUI_APP=0 CXX=x86_64-linux-gnu-g++-11 && sudo make install - name: Test neofetch run: neofetch @@ -128,23 +93,25 @@ jobs: name: customfetch path: ./build/debug/customfetch - build_ubuntu-20_04-GUI: - runs-on: ubuntu-20.04 + build-ubuntu-22_04-GUI: + runs-on: ubuntu-22.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 - name: Install Packages run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test + sudo apt-get purge firefox # Slows down the installation alot, fuck snap sudo apt-get update && sudo apt upgrade -y - sudo apt-get install neofetch tree build-essential g++-10 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev -y + sudo apt-get install neofetch tree build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev -y - name: Clean - run: make clean + run: make distclean - name: Compile and install - run: make DEBUG=1 GUI_APP=1 CXX=x86_64-linux-gnu-g++-10 && sudo make install GUI_APP=1 + run: make DEBUG=1 GUI_APP=1 CXX=x86_64-linux-gnu-g++-11 && sudo make install GUI_APP=1 - name: Test neofetch run: neofetch @@ -161,6 +128,8 @@ jobs: build_Arch-AUR: runs-on: ubuntu-latest + permissions: + contents: read container: image: archlinux @@ -168,7 +137,7 @@ jobs: - uses: actions/checkout@v4 - name: Install the packages - run: pacman -Syyu git sudo base-devel fakeroot pkgconf tree neofetch --noconfirm --needed + run: pacman -Syyu git sudo base-devel fakeroot pkgconf tree --noconfirm --needed - name: Create a dedicated build user run: | @@ -182,6 +151,11 @@ jobs: - name: Build and install using makepkg run: | + git clone https://aur.archlinux.org/customfetch-common-git.git + chown -R builduser customfetch-common-git + cd customfetch-common-git + sudo -u builduser makepkg -si --noconfirm + cd .. git clone https://aur.archlinux.org/customfetch-git.git chown -R builduser customfetch-git cd customfetch-git @@ -190,14 +164,13 @@ jobs: #- name: Setup SSH session # uses: mxschmitt/action-tmate@v3 - - name: Test neofetch - run: neofetch - - name: Test customfetch run: customfetch --wrap-lines build_Arch: runs-on: ubuntu-latest + permissions: + contents: read container: image: archlinux @@ -205,7 +178,7 @@ jobs: - uses: actions/checkout@v4 - name: Install the packages - run: pacman -Syyu git sudo binutils gdb base-devel fakeroot pkgconf tree neofetch fastfetch --noconfirm --needed + run: pacman -Syyu git sudo binutils gdb base-devel fakeroot pkgconf tree fastfetch --noconfirm --needed - name: get /etc/sudoers run: | @@ -217,13 +190,10 @@ jobs: cat /etc/makepkg.conf - name: Clean - run: make clean + run: make distclean - name: Compile and install - run: make install DEBUG=1 VENDOR_TEST=1 GUI_APP=0 - - - name: Test neofetch - run: neofetch + run: make install DEBUG=1 GUI_APP=0 - name: Test fastfetch run: fastfetch @@ -239,49 +209,60 @@ jobs: - name: Test customfetch run: ./build/debug/customfetch --wrap-lines - build-android-app: - runs-on: ubuntu-latest - + build-macos: + runs-on: macos-latest + permissions: + contents: read + steps: - uses: actions/checkout@v4 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Grant execute permission for gradlew - run: chmod +x android/gradlew - - - name: Build with Gradle - run: make distclean && make android_app - - - name: Check files - run: tree android/app/build/outputs/apk - - name: Upload to github artifacts - uses: actions/upload-artifact@v4 - with: - if-no-files-found: error - name: customfetch-android-app - path: android/app/build/outputs/apk/debug/app-debug.apk + - name: Install the packages + run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite fastfetch neofetch tree llvm@19 - test-all-ascii-art: - runs-on: ubuntu-latest + - name: uname -a && clang++ --version + run: uname -a && echo "\n" && $(brew --prefix llvm@19)/bin/clang++ --version + + - name: Clean + run: make distclean - steps: - - uses: actions/checkout@v4 + - name: Compile + run: make DEBUG=1 GUI_APP=0 CXX=$(brew --prefix llvm@19)/bin/clang++ - - name: Install Packages - run: sudo apt-get update && sudo apt-get install build-essential gettext tree pkg-config -y - - - name: Clean - run: make clean + - name: Test neofetch + run: neofetch - - name: Compile and install - run: sudo make install DEBUG=0 VENDOR_TEST=0 GUI_APP=0 + - name: Test fastfetch + run: fastfetch + + - name: Test customfetch + run: ./build/debug/customfetch -D assets - - name: Test all the ascii art logos - run: for f in assets/ascii/*; do printf "\e[31m%s\e[0m\n" "$f" && customfetch --wrap-lines --source "$f" -D assets && sleep 1; done + - name: Upload to github artifacts + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: customfetch-macos + path: ./build/debug/customfetch + + # test-suitcase: + # runs-on: ubuntu-22.04 + # permissions: + # contents: read + # + # steps: + # - uses: actions/checkout@v4 + # + # - name: Install Packages + # run: | + # sudo apt-get purge firefox # Slows down the installation alot, fuck snap + # sudo apt-get update && sudo apt upgrade -y + # sudo apt-get install neofetch tree build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y + # + # - name: Clean + # run: make distclean + # + # - name: Compile and install + # run: | + # cd tests && make + # find . -type f -executable -exec {} \; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a5247bd..0fc78eca 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,13 +3,15 @@ name: Release on: push: tags: "*" - paths: + #paths: # trigger release workflow only if this file changed - - .github/workflows/release.yml + #- .github/workflows/release.yml jobs: get-version: runs-on: ubuntu-latest + permissions: + contents: read outputs: version: ${{ steps.version.outputs.version }} steps: @@ -19,176 +21,127 @@ jobs: echo "Tag name from github.ref_name: ${{ github.ref_name }}" echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT - build-tar: - runs-on: ubuntu-20.04 + build-linux-tar: + runs-on: ubuntu-22.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 - name: Install Packages run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test sudo apt-get update && sudo apt upgrade -y - sudo apt-get install build-essential g++-10 libwayland-dev gettext pkg-config libarchive-tools libdconf-dev libglib2.0-dev -y - - - name: Clean - run: make clean + sudo apt-get install build-essential g++-11 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y - name: Compile - run: make DEBUG=0 GUI_APP=0 CXX=x86_64-linux-gnu-g++-10 - - - name: Make Release - run: make usr-dist DEBUG=0 && mv ./customfetch-*.tar.gz ./customfetch.tar.gz - - - name: Upload to github artifacts - uses: actions/upload-artifact@v4 - with: - name: customfetch - path: ./customfetch.tar.gz + run: make DEBUG=0 GUI_APP=0 CXX=x86_64-linux-gnu-g++-11 - build-gui-tar: - runs-on: ubuntu-20.04 + - name: Install to ./usr + run: mkdir -p ./usr && make install DEBUG=0 DESTDIR=$(pwd) PREFIX=/usr - steps: - - uses: actions/checkout@v4 + - name: Compile (GUI app) + run: make clean && make DEBUG=0 GUI_APP=1 CXX=x86_64-linux-gnu-g++-11 - - name: Install Packages + - name: Install the GUI components run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update && sudo apt upgrade -y - sudo apt-get install build-essential g++-10 libwayland-dev gettext libgtk-3-dev pkg-config libdconf-dev libglib2.0-dev libgtkmm-3.0-dev libarchive-tools -y - - - name: Clean - run: make clean - - - name: Compile - run: make DEBUG=0 GUI_APP=1 CXX=x86_64-linux-gnu-g++-10 + install ./build/release/customfetch-gui -Dm 755 -v ./usr/bin/ + mkdir -p ./usr/share/applications + install -Dm644 customfetch.desktop ./usr/share/applications/ - name: Make Release - run: make usr-dist DEBUG=0 GUI_APP=1 && mv ./customfetch-*.tar.gz ./customfetch-gui.tar.gz + run: tar -zcf ./customfetch.tar.gz usr/ - name: Upload to github artifacts uses: actions/upload-artifact@v4 with: - name: customfetch-gui - path: ./customfetch-gui.tar.gz + name: customfetch-linux + path: ./customfetch.tar.gz build-deb: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 + permissions: + contents: read steps: - uses: actions/checkout@v4 - + - name: create tmp directory - run: mkdir /tmp/customfetch-1.0.0.orig + run: mkdir /tmp/customfetch-2.0.0-beta1.orig - name: Install Packages run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get upgrade -y - sudo apt-get install libwayland-dev g++-10 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools + sudo apt-get update && sudo apt-get upgrade -y + sudo apt-get install libwayland-dev g++-11 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools sudo apt-get install binutils gettext lintian debhelper devscripts debmake autoconf automake autotools-dev dh-make fakeroot xutils pbuilder -y - name: Clean - run: make clean + run: make distclean - name: Create deb run: | - cp -r $GITHUB_WORKSPACE /tmp/customfetch-1.0.0.orig - cd /tmp/customfetch-1.0.0.orig/customfetch + cp -r $GITHUB_WORKSPACE /tmp/customfetch-2.0.0-beta1.orig + cd /tmp/customfetch-2.0.0-beta1.orig/customfetch mkdir -p Debian/Debhelper/Buildsystem/ wget https://github.com/Debian/debhelper/raw/master/lib/Debian/Debhelper/Buildsystem/makefile.pm -O Debian/Debhelper/Buildsystem/make.pm sed -i "s#package Debian::Debhelper::Buildsystem::makefile#package Debian::Debhelper::Buildsystem::make#g" Debian/Debhelper/Buildsystem/make.pm - sed -i "s#DEBUG=0#DEBUG=0 CXX=x86_64-linux-gnu-g++-10#g" debian/rules + sed -i "s#DEBUG=#CXX=x86_64-linux-gnu-g++-11 DEBUG=#g" debian/rules dpkg-buildpackage -us -uc - mv ../customfetch_1.0.0-1_amd64.deb ../customfetch_amd64.deb + mv ../customfetch_2.0.0-beta1_amd64.deb ../customfetch_amd64.deb - name: Upload to github artifacts uses: actions/upload-artifact@v4 with: name: customfetch-deb-pkg - path: /tmp/customfetch-1.0.0.orig/customfetch_amd64.deb - - build-deb-gui: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - - - name: create tmp directory - run: mkdir /tmp/customfetch-1.0.0.orig - - - name: Install Packages - run: | - sudo add-apt-repository ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get upgrade -y - sudo apt-get install libwayland-dev g++-10 libgtk-3-dev pkg-config libgtkmm-3.0-dev libdconf-dev libglib2.0-dev libarchive-tools - sudo apt-get install binutils lintian debhelper gettext devscripts debmake autoconf automake autotools-dev dh-make fakeroot xutils pbuilder -y - - - name: Clean - run: make clean + path: /tmp/customfetch-2.0.0-beta1.orig/customfetch_amd64.deb - - name: Create deb - run: | - cp -r $GITHUB_WORKSPACE /tmp/customfetch-1.0.0.orig - cd /tmp/customfetch-1.0.0.orig/customfetch - mkdir -p Debian/Debhelper/Buildsystem/ - wget https://github.com/Debian/debhelper/raw/master/lib/Debian/Debhelper/Buildsystem/makefile.pm -O Debian/Debhelper/Buildsystem/make.pm - sed -i "s#package Debian::Debhelper::Buildsystem::makefile#package Debian::Debhelper::Buildsystem::make#g" Debian/Debhelper/Buildsystem/make.pm - sed -i "s#GUI_APP=0#GUI_APP=1 CXX=x86_64-linux-gnu-g++-10#g" debian/rules - sed -i "/^Build-Depends:/a Depends: libgtkmm-3.0-1v5,libgtk-3-0" debian/control - dpkg-buildpackage -us -uc - mv ../customfetch_1.0.0-1_amd64.deb ../customfetch-gui_amd64.deb - - - name: Upload to github artifacts - uses: actions/upload-artifact@v4 - with: - name: customfetch-gui-deb-pkg - path: /tmp/customfetch-1.0.0.orig/customfetch-gui_amd64.deb + build-macos-tar: + runs-on: macos-latest + permissions: + contents: read - build-android-app: - runs-on: ubuntu-latest - steps: - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - cache: gradle - - - name: Create signing.properties - run: | - echo -n ${{ secrets.KEYSTORE_SIGNING_KEY }} > /tmp/keystore.txt && base64 -d /tmp/keystore.txt > /tmp/keystore.key - cat << EOF > android/signing.properties - storeFilePath=/tmp/keystore.key - storePassword=${{ secrets.KEYSTORE_PASSWORD }} - keyPassword=${{ secrets.KEYSTORE_PASSWORD }} - keyAlias=${{ secrets.KEYSTORE_ALIAS }} - EOF - - - name: Grant execute permission for gradlew - run: chmod +x android/gradlew + - name: Install the packages + run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install --overwrite fastfetch neofetch pkg-config gtkmm3 tree llvm@19 - - name: Build with Gradle - run: make distclean && make android_app DEBUG=0 + - name: uname -a && clang++ --version + run: uname -a && echo "\n" && $(brew --prefix llvm@19)/bin/clang++ --version - - name: Check files - run: tree android/app/build/outputs/apk + - name: Compile and install + run: | + export CXX=$(brew --prefix llvm@19)/bin/clang++ + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DGUI_APP=0 -DCMAKE_INSTALL_PREFIX=../usr + make + make install + cd .. + + - name: Compile (GUI app) + run: | + export CXX=$(brew --prefix llvm@19)/bin/clang++ + export PKG_CONFIG_PATH="$(brew --prefix)/lib/pkgconfig:$(brew --prefix)/opt/gtkmm3/lib/pkgconfig:$(brew --prefix)/opt/gtk+3/lib/pkgconfig" + rm -rf build && mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DGUI_APP=1 -DCMAKE_INSTALL_PREFIX=../usr + make + mv -f customfetch-gui ../usr/bin/ + mkdir -p ../usr/share/applications + mv -f ../customfetch.desktop ../usr/share/applications + + - name: Make release + run: tar -zcf ./customfetch.tar.gz usr/ - name: Upload to github artifacts uses: actions/upload-artifact@v4 with: - name: customfetch-android-app - path: android/app/build/outputs/apk/release/app-release.apk - + if-no-files-found: error + name: customfetch-macos + path: ./customfetch.tar.gz + release: name: Create GitHub Release - needs: [build-tar, build-gui-tar, build-deb-gui, build-deb, build-android-app, get-version] + needs: [build-linux-tar, build-macos-tar, build-deb, get-version] runs-on: ubuntu-latest permissions: contents: write @@ -223,8 +176,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.release.outputs.release-url }} - asset_path: customfetch/customfetch.tar.gz - asset_name: customfetch-${{ needs.get-version.outputs.version }}.tar.gz + asset_path: customfetch-linux/customfetch.tar.gz + asset_name: customfetch-linux-${{ needs.get-version.outputs.version }}.tar.gz asset_content_type: application/tar+gz - uses: actions/upload-release-asset@v1 @@ -232,8 +185,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ needs.release.outputs.release-url }} - asset_path: customfetch-gui/customfetch-gui.tar.gz - asset_name: customfetch-gui-${{ needs.get-version.outputs.version }}.tar.gz + asset_path: customfetch-macos/customfetch.tar.gz + asset_name: customfetch-macos-${{ needs.get-version.outputs.version }}.tar.gz asset_content_type: application/tar+gz - uses: actions/upload-release-asset@v1 @@ -244,21 +197,3 @@ jobs: asset_path: customfetch-deb-pkg/customfetch_amd64.deb asset_name: customfetch_${{ needs.get-version.outputs.version }}_amd64.deb asset_content_type: application/vnd.debian.binary-package - - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.release-url }} - asset_path: customfetch-gui-deb-pkg/customfetch-gui_amd64.deb - asset_name: customfetch-gui_${{ needs.get-version.outputs.version }}_amd64.deb - asset_content_type: application/vnd.debian.binary-package - - - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.release.outputs.release-url }} - asset_path: customfetch-android-app/app-release.apk - asset_name: customfetch-android-app-${{ needs.get-version.outputs.version }}.apk - asset_content_type: application/vnd.android.package-archive diff --git a/.gitignore b/.gitignore index cebb5397..42a5f20c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,19 +1,20 @@ build*/ -assets/distro_ascii_arts.json -assets/distro_ascii_arts.txt -cufetch -test*.txt +assets/distro_ascii* +/cufetch +/*.txt pci.ids output/ useless_stuff/ -customfetch -customfetch_r -cufetch_r +/customfetch +/customfetch_r +/cufetch_r locale/ usr/ -scripts/ascii_parser.py -scripts/dict_to_files.py -scripts/test* +/scripts/ascii_parser.py +/scripts/dict_to_files.py +/scripts/test* +tests/test* +!tests/*.cpp *.tar.* *.mo @@ -52,3 +53,4 @@ scripts/test* *.app .cache signing.properties +include/version.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bf06216..169d9ded 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,46 +6,76 @@ cmake_minimum_required(VERSION 3.10) project(customfetch) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -O0 -DDEBUG=1 -Wall -Wextra -Wpedantic -Wno-unused-parameter") +set(CMAKE_CXX_VISIBILITY_PRESET hidden) +set(CMAKE_VISIBILITY_INLINES_HIDDEN ON) include_directories(include) +include_directories(include/libcufetch) +include_directories(include/libs) + +set_source_files_properties( + "src/libs/toml++/toml.cpp" + "src/libs/tiny-process-library/process.cpp" + PROPERTIES COMPILE_FLAGS "-fvisibility=default" +) file(GLOB SRC "src/*.cpp" - "src/query/linux/*.cpp" - "src/query/android/*.cpp" - "src/query/linux/utils/*.cpp" - "src/toml++/toml.cpp") + "src/core-modules/*.cc" + "src/core-modules/linux/*.cc" + "src/core-modules/android/*.cc" + "src/core-modules/macos/*.cc" + "src/core-modules/linux/utils/*.cc" + "src/libs/json/json.cpp" + "src/libs/toml++/toml.cpp" + "src/libs/getopt_port/getopt.c") + +function(enable_lto target) + if (NOT TARGET ${target}) + message(FATAL_ERROR "enable_lto called on non-existent target: ${target}") + endif() + + if (CMAKE_BUILD_TYPE STREQUAL "Release") + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC supports -ffat-lto-objects for archives + target_compile_options(${target} PRIVATE -flto=auto -ffat-lto-objects) + target_link_options(${target} PRIVATE -flto=auto) + elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Use ThinLTO on Clang (better incremental builds) + target_compile_options(${target} PRIVATE -flto=thin) + target_link_options(${target} PRIVATE -flto=thin) + else() + message(WARNING "LTO not configured for compiler: ${CMAKE_CXX_COMPILER_ID}") + endif() + endif() +endfunction() if(GUI_APP) -set(TARGET_NAME customfetch-gui) + set(TARGET_NAME customfetch-gui) else() -set(TARGET_NAME customfetch) + set(TARGET_NAME customfetch) endif() add_executable(${TARGET_NAME} ${SRC}) +enable_lto(${TARGET_NAME}) -# get git branch -execute_process( - COMMAND git rev-parse --abbrev-ref HEAD - OUTPUT_VARIABLE GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE -) +if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "-Wl,-rpath,${ORIGIN}/") +endif() -set(VERSION "1.0.0") -target_compile_definitions(${TARGET_NAME} PRIVATE - VERSION="${VERSION}" - BRANCH="${GIT_BRANCH}" -) +# Get git info hash and branch +execute_process(COMMAND ./scripts/generateVersion.sh + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) # https://github.com/libcpr/cpr/blob/5f475522597b8f3721e2440daddeced7a969f24c/CMakeLists.txt#L39 -macro(add_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT) +macro(add_option OPTION_NAME OPTION_TEXT OPTION_DEFAULT DEFINE_OPTION) option(${OPTION_NAME} ${OPTION_TEXT} ${OPTION_DEFAULT}) if(DEFINED ENV{${OPTION_NAME}}) # Allow overriding the option through an environment variable set(${OPTION_NAME} $ENV{${OPTION_NAME}}) endif() - if(${OPTION_NAME}) + if(${OPTION_NAME} AND DEFINE_OPTION) add_definitions(-D${OPTION_NAME}) endif() message(STATUS " ${OPTION_NAME}: ${${OPTION_NAME}}") @@ -53,31 +83,243 @@ endmacro() message(STATUS "Set flags:") message(STATUS "=================") -add_option(GUI_APP "Build GTK3 application" 0) -add_option(USE_DCONF "Compile customfetch with dconf support" 1) -add_option(VARS "Add additional flags at CXXFLAGS" "") +add_option(GUI_APP "Build GTK3 application" 0 1) +add_option(USE_DCONF "Compile customfetch with dconf support" 1 0) +add_option(VARS "Add additional flags at CXXFLAGS" "" 1) message(STATUS "=================") if(GUI_APP) - find_package(PkgConfig REQUIRED) - pkg_check_modules(GTKMM REQUIRED gtkmm-3.0 gdkmm-3.0) - target_link_libraries(${TARGET_NAME} PUBLIC ${GTKMM_LIBRARIES}) - target_include_directories(${TARGET_NAME} PUBLIC ${GTKMM_INCLUDE_DIRS}) - target_compile_options(${TARGET_NAME} PUBLIC ${GTKMM_CFLAGS_OTHER}) + find_package(PkgConfig REQUIRED) + pkg_check_modules(GTKMM REQUIRED gtkmm-3.0 gdkmm-3.0) + + target_include_directories(${TARGET_NAME} PUBLIC ${GTKMM_INCLUDE_DIRS}) + target_compile_options(${TARGET_NAME} PUBLIC ${GTKMM_CFLAGS_OTHER}) + target_link_directories(${TARGET_NAME} PUBLIC ${GTKMM_LIBRARY_DIRS}) + target_link_libraries(${TARGET_NAME} PUBLIC ${GTKMM_LIBRARIES}) endif() if(USE_DCONF) - find_package(PkgConfig REQUIRED) - pkg_check_modules(DCONF dconf) - target_link_libraries(${TARGET_NAME} PUBLIC ${DCONF_LIBRARIES}) + find_package(PkgConfig REQUIRED) + pkg_check_modules(DCONF dconf) + + if(DCONF_FOUND) + message(STATUS "Found dconf - enabling dconf support") + target_compile_definitions(${TARGET_NAME} PRIVATE USE_DCONF=1) target_include_directories(${TARGET_NAME} PUBLIC ${DCONF_INCLUDE_DIRS}) target_compile_options(${TARGET_NAME} PUBLIC ${DCONF_CFLAGS_OTHER}) + else() + message(STATUS "dconf not found - disabling dconf support") + target_compile_definitions(${TARGET_NAME} PRIVATE USE_DCONF=0) + endif() +else() + target_compile_definitions(${TARGET_NAME} PRIVATE USE_DCONF=0) endif() - # fmt add_library(fmt STATIC - "src/fmt/os.cc" - "src/fmt/format.cc") + "src/libs/fmt/os.cc" + "src/libs/fmt/format.cc") +set_target_properties(fmt PROPERTIES POSITION_INDEPENDENT_CODE ON) +enable_lto(fmt) target_link_libraries(${TARGET_NAME} PUBLIC fmt) + +# tiny-process-library (integrated from its own CMakeLists.txt) +add_library(tiny-process-library STATIC + "src/libs/tiny-process-library/process.cpp") +add_library(tiny-process-library::tiny-process-library ALIAS tiny-process-library) + +if(MSVC) + target_compile_definitions(tiny-process-library PRIVATE /D_CRT_SECURE_NO_WARNINGS) +else() + target_compile_options(tiny-process-library PRIVATE -std=c++11 -Wall -Wextra) +endif() + +if(WIN32) + set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) + target_sources(tiny-process-library PRIVATE "src/libs/tiny-process-library/process_win.cpp") + # If compiled using MSYS2, use sh to run commands + if(MSYS) + target_compile_definitions(tiny-process-library PUBLIC MSYS_PROCESS_USE_SH) + endif() +else() + target_sources(tiny-process-library PRIVATE "src/libs/tiny-process-library/process_unix.cpp") +endif() + +set_target_properties(tiny-process-library PROPERTIES + POSITION_INDEPENDENT_CODE ON +) + +find_package(Threads REQUIRED) +target_link_libraries(tiny-process-library Threads::Threads) +target_include_directories(tiny-process-library PUBLIC + $ + $ +) +enable_lto(tiny-process-library) +target_link_libraries(${TARGET_NAME} PUBLIC tiny-process-library) + +# libcufetch +set(CUFETCH_HEADERS + include/libcufetch/config.hh + include/libcufetch/common.hh + include/libcufetch/cufetch.hh +) + +add_library(cufetch SHARED + libcufetch/cufetch.cc + libcufetch/parse.cc + src/libs/toml++/toml.cpp + src/util.cpp +) + +set_target_properties(cufetch PROPERTIES + VERSION 2.0.0 + SOVERSION 2 + OUTPUT_NAME "cufetch" + POSITION_INDEPENDENT_CODE ON +) + +target_link_libraries(cufetch PUBLIC tiny-process-library fmt) +target_link_libraries(${TARGET_NAME} PUBLIC cufetch) + +# cufetchpm +add_executable(cufetchpm + cufetchpm/src/main.cpp + cufetchpm/src/pluginManager.cpp + cufetchpm/src/manifest.cpp + cufetchpm/src/stateManager.cpp + src/util.cpp + src/libs/toml++/toml.cpp + src/libs/getopt_port/getopt.c +) + +set_target_properties(cufetchpm PROPERTIES + OUTPUT_NAME "cufetchpm" +) + +target_include_directories(cufetchpm PUBLIC + cufetchpm/include + ../include + ../include/libs +) + +target_compile_definitions(cufetchpm PRIVATE + VERSION="2.0.0-beta1" +) + +target_link_libraries(cufetchpm + fmt + tiny-process-library +) + +# locale +add_custom_target(locale + COMMAND ${CMAKE_SOURCE_DIR}/scripts/make_mo.sh ${CMAKE_SOURCE_DIR}/locale/ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating locale message object files" +) + +add_dependencies(${TARGET_NAME} locale) + +# Installation prefixes +set(MANPREFIX "${CMAKE_INSTALL_PREFIX}/share/man" CACHE PATH "Man page installation directory") +set(APPPREFIX "${CMAKE_INSTALL_PREFIX}/share/applications" CACHE PATH "Application desktop file directory") +set(LOCALEDIR "${CMAKE_INSTALL_PREFIX}/share/locale" CACHE PATH "Locale files directory") +set(ICONPREFIX "${CMAKE_INSTALL_PREFIX}/share/pixmaps" CACHE PATH "Icon installation directory") +target_compile_definitions(${TARGET_NAME} PRIVATE + VERSION="2.0.0-beta1" + MANPREFIX="${MANPREFIX}" + APPPREFIX="${APPPREFIX}" + LOCALEDIR="${LOCALEDIR}" + ICONPREFIX="${ICONPREFIX}" + $<$:GUI_APP=1> +) + +# Install executable +install(TARGETS ${TARGET_NAME} + RUNTIME DESTINATION bin + COMPONENT runtime) + +install(TARGETS cufetchpm + RUNTIME DESTINATION bin + COMPONENT runtime) + +# Create symlink +install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink ${TARGET_NAME} \${CMAKE_INSTALL_PREFIX}/bin/cufetch)") + +# Install the rest +install(FILES ${CMAKE_SOURCE_DIR}/customfetch.1 + DESTINATION share/man/man1 + COMPONENT documentation) + +install(FILES ${CMAKE_SOURCE_DIR}/LICENSE + DESTINATION share/licenses/customfetch + COMPONENT documentation) + +install(FILES $ + DESTINATION lib + RENAME libcufetch-fmt.a + COMPONENT runtime +) + +install(TARGETS cufetch + LIBRARY DESTINATION lib + COMPONENT runtime +) + +install(DIRECTORY include/libcufetch/ + DESTINATION include/libcufetch + COMPONENT development +) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets/ascii/ + DESTINATION share/customfetch/ascii + COMPONENT runtime) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/examples/ + DESTINATION share/customfetch/examples + COMPONENT runtime) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/assets/icons/ + DESTINATION share/pixmaps/customfetch + COMPONENT runtime) + +install(DIRECTORY ${CMAKE_SOURCE_DIR}/locale/ + DESTINATION share/locale + COMPONENT runtime) + +if(NOT APPLE) +install(CODE " + execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink libcufetch.so.2 ${CMAKE_INSTALL_PREFIX}/lib/libcufetch.so + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E create_symlink libcufetch.so.2.0.0 ${CMAKE_INSTALL_PREFIX}/lib/libcufetch.so.2 + ) +") +endif() + +install(CODE " + execute_process( + COMMAND ${CMAKE_SOURCE_DIR}/scripts/make_mo.sh ${CMAKE_SOURCE_DIR}/locale/ + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + ) +" COMPONENT runtime) + +if(GUI_APP) + install(FILES ${CMAKE_SOURCE_DIR}/customfetch.desktop + DESTINATION share/applications + COMPONENT runtime) +endif() + +# Uninstall target +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() diff --git a/LICENSE b/LICENSE index 8969dbb7..e566ba5b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ BSD 3-Clause License -Copyright (c) 2024, Toni500git +Copyright (c) 2025, Toni500git Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/LICENSE.fastfetch.MIT b/LICENSE.fastfetch.MIT new file mode 100644 index 00000000..b2f097ce --- /dev/null +++ b/LICENSE.fastfetch.MIT @@ -0,0 +1,24 @@ +Portions of customfetch system infos queries, that's based/similiar to fastfetch code, are used under the following license: + +MIT License + +Copyright (c) 2021-2023 Linus Dierheimer +Copyright (c) 2022-2025 Carter Li + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile index 961ddf35..899321ca 100644 --- a/Makefile +++ b/Makefile @@ -4,19 +4,32 @@ PREFIX ?= /usr MANPREFIX ?= $(PREFIX)/share/man APPPREFIX ?= $(PREFIX)/share/applications LOCALEDIR ?= $(PREFIX)/share/locale +ICONPREFIX ?= $(PREFIX)/share/pixmaps VARS ?= -DENABLE_NLS=1 +CXXSTD ?= c++20 DEBUG ?= 1 GUI_APP ?= 0 -VENDOR_TEST ?= 0 -DEVICE_TEST ?= 0 - USE_DCONF ?= 1 + +COMPILER := $(shell $(CXX) --version | head -n1) + +ifeq ($(findstring g++,$(COMPILER)),g++) + export LTO_FLAGS = -flto=auto -ffat-lto-objects +else ifeq ($(findstring clang,$(COMPILER)),clang) + export LTO_FLAGS = -flto=thin +else + $(warning Unknown compiler: $(COMPILER). No LTO flags applied.) +endif + # https://stackoverflow.com/a/1079861 # WAY easier way to build debug and release builds ifeq ($(DEBUG), 1) - BUILDDIR = build/debug - CXXFLAGS := -ggdb3 -Wall -Wextra -Wpedantic -Wno-unused-parameter -DDEBUG=1 $(DEBUG_CXXFLAGS) $(CXXFLAGS) + BUILDDIR := build/debug + LTO_FLAGS = -fno-lto + CXXFLAGS := -ggdb3 -Wall -Wextra -pedantic -Wno-unused-parameter -fsanitize=address \ + -DDEBUG=1 -fno-omit-frame-pointer $(DEBUG_CXXFLAGS) $(CXXFLAGS) + LDFLAGS += -fsanitize=address -fno-lto -Wl,-rpath,$(BUILDDIR) else # Check if an optimization flag is not already set ifneq ($(filter -O%,$(CXXFLAGS)),) @@ -24,15 +37,8 @@ else else CXXFLAGS := -O3 $(CXXFLAGS) endif - BUILDDIR = build/release -endif - -ifeq ($(VENDOR_TEST), 1) - VARS += -DVENDOR_TEST=1 -endif - -ifeq ($(DEVICE_TEST), 1) - VARS += -DDEVICE_TEST=1 + LDFLAGS += $(LTO_FLAGS) + BUILDDIR := build/release endif ifeq ($(GUI_APP), 1) @@ -52,43 +58,61 @@ endif NAME = customfetch TARGET ?= $(NAME) -OLDVERSION = 0.10.2 -VERSION = 1.0.0 -BRANCH = $(shell git rev-parse --abbrev-ref HEAD) -SRC = $(wildcard src/*.cpp src/query/linux/*.cpp src/query/android/*.cpp src/query/linux/utils/*.cpp) -OBJ = $(SRC:.cpp=.o) -LDFLAGS += -L./$(BUILDDIR)/fmt -lfmt -ldl +OLDVERSION = 1.0.0 +VERSION = 2.0.0-beta1 +SRC_CPP = $(wildcard src/*.cpp) +SRC_CC = $(wildcard src/core-modules/*.cc src/core-modules/linux/*.cc src/core-modules/linux/utils/*.cc src/core-modules/android/*.cc src/core-modules/macos/*.cc) +OBJ_CPP = $(SRC_CPP:.cpp=.o) +OBJ_CC = $(SRC_CC:.cc=.o) +OBJ = $(OBJ_CPP) $(OBJ_CC) +LDFLAGS += -L$(BUILDDIR) +LDLIBS += $(BUILDDIR)/libfmt.a $(BUILDDIR)/libtiny-process-library.a -lcufetch -ldl CXXFLAGS ?= -mtune=generic -march=native -CXXFLAGS += -fvisibility=hidden -Iinclude -std=c++20 $(VARS) -DVERSION=\"$(VERSION)\" -DBRANCH=\"$(BRANCH)\" -DLOCALEDIR=\"$(LOCALEDIR)\" +CXXFLAGS += $(LTO_FLAGS) -fvisibility-inlines-hidden -fvisibility=hidden -Iinclude -Iinclude/libcufetch -Iinclude/libs -std=$(CXXSTD) $(VARS) -DVERSION=\"$(VERSION)\" -DLOCALEDIR=\"$(LOCALEDIR)\" -DICONPREFIX=\"$(ICONPREFIX)\" -all: fmt toml $(TARGET) +all: genver fmt toml tpl getopt-port json libcufetch $(TARGET) + +libcufetch: fmt tpl toml +ifeq ($(wildcard $(BUILDDIR)/libcufetch.so),) + $(MAKE) -C libcufetch BUILDDIR=$(BUILDDIR) GUI_APP=$(GUI_APP) CXXSTD=$(CXXSTD) +endif fmt: -ifeq ($(wildcard $(BUILDDIR)/fmt/libfmt.a),) - mkdir -p $(BUILDDIR)/fmt - make -C src/fmt BUILDDIR=$(BUILDDIR)/fmt +ifeq ($(wildcard $(BUILDDIR)/libfmt.a),) + mkdir -p $(BUILDDIR) + $(MAKE) -C src/libs/fmt BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) endif toml: -ifeq ($(wildcard $(BUILDDIR)/toml++/toml.o),) - mkdir -p $(BUILDDIR)/toml++ - make -C src/toml++ BUILDDIR=$(BUILDDIR)/toml++ +ifeq ($(wildcard $(BUILDDIR)/toml.o),) + $(MAKE) -C src/libs/toml++ BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) endif -$(TARGET): fmt toml $(OBJ) - mkdir -p $(BUILDDIR) - $(CXX) $(OBJ) $(BUILDDIR)/toml++/toml.o -o $(BUILDDIR)/$(TARGET) $(LDFLAGS) - cd $(BUILDDIR)/ && ln -sf $(TARGET) cufetch +tpl: +ifeq ($(wildcard $(BUILDDIR)/libtiny-process-library.a),) + $(MAKE) -C src/libs/tiny-process-library BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) +endif -android_app: -ifeq ($(DEBUG), 1) - ./android/gradlew assembleDebug --project-dir=./android -else - ./android/gradlew assembleRelease --project-dir=./android +getopt-port: +ifeq ($(wildcard $(BUILDDIR)/getopt.o),) + $(MAKE) -C src/libs/getopt_port BUILDDIR=$(BUILDDIR) +endif + +json: +ifeq ($(wildcard $(BUILDDIR)/json.o),) + $(MAKE) -C src/libs/json BUILDDIR=$(BUILDDIR) CXXSTD=$(CXXSTD) endif - @if [ $$? -eq 0 ]; then\ - echo "APK build successfully. Get it in $(CURDIR)/android/app/build/outputs/apk path and choose which to install (debug/release)";\ - fi + +genver: ./scripts/generateVersion.sh +ifeq ($(wildcard include/version.h),) + ./scripts/generateVersion.sh +endif + +$(TARGET): genver fmt toml tpl getopt-port json libcufetch $(OBJ) + mkdir -p $(BUILDDIR) + sh ./scripts/generateVersion.sh + $(CXX) -o $(BUILDDIR)/$(TARGET) $(OBJ) $(BUILDDIR)/*.o $(LDFLAGS) $(LDLIBS) + cd $(BUILDDIR)/ && ln -sf $(TARGET) cufetch locale: scripts/make_mo.sh locale/ @@ -102,29 +126,38 @@ endif usr-dist: $(TARGET) locale mkdir -p $(PWD)/usr - make install DESTDIR=$(PWD) PREFIX=/usr + $(MAKE) install DESTDIR=$(PWD) PREFIX=/usr $(TAR) -zcf $(NAME)-v$(VERSION).tar.gz usr/ rm -rf usr/ clean: - rm -rf $(BUILDDIR)/$(TARGET) $(BUILDDIR)/libcustomfetch.a $(OBJ) + rm -rf $(BUILDDIR)/$(TARGET) $(BUILDDIR)/libcufetch.so $(OBJ) libcufetch/*.o distclean: rm -rf $(BUILDDIR) ./tests/$(BUILDDIR) $(OBJ) - find . -type f -name "*.tar.gz" -exec rm -rf "{}" \; - find . -type f -name "*.o" -exec rm -rf "{}" \; - find . -type f -name "*.a" -exec rm -rf "{}" \; + find . -type f -name "*.tar.gz" -delete + find . -type f -name "*.o" -delete + find . -type f -name "*.a" -delete -install: $(TARGET) locale +install: install-common $(TARGET) install $(BUILDDIR)/$(TARGET) -Dm 755 -v $(DESTDIR)$(PREFIX)/bin/$(TARGET) cd $(DESTDIR)$(PREFIX)/bin/ && ln -sf $(TARGET) cufetch + +install-common: genver libcufetch locale + $(MAKE) -C cufetchpm DEBUG=$(DEBUG) GUI_APP=$(GUI_APP) CXXSTD=$(CXXSTD) + install cufetchpm/$(BUILDDIR)/cufetchpm -Dm 755 -v $(DESTDIR)$(PREFIX)/bin/cufetchpm mkdir -p $(DESTDIR)$(MANPREFIX)/man1/ sed -e "s/@VERSION@/$(VERSION)/g" -e "s/@BRANCH@/$(BRANCH)/g" < $(NAME).1 > $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 install LICENSE -Dm 644 $(DESTDIR)$(PREFIX)/share/licenses/$(NAME)/LICENSE - cd assets/ && find ascii/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/customfetch/{}" \; - find examples/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/customfetch/{}" \; + cd assets/ && find ascii/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/$(NAME)/{}" \; + cd assets/icons && find . -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(ICONPREFIX)/$(NAME)/{}" \; + find examples/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/$(NAME)/{}" \; find locale/ -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/share/{}" \; + mkdir -p $(DESTDIR)$(PREFIX)/include/libcufetch/ + cd include/libcufetch && find . -type f -exec install -Dm 644 "{}" "$(DESTDIR)$(PREFIX)/include/libcufetch/{}" \; + cd $(BUILDDIR) && find . -name "libcufetch*" -exec install -Dm 755 "{}" "$(DESTDIR)$(PREFIX)/lib/{}" \; + install -Dm 755 $(BUILDDIR)/libfmt.a $(DESTDIR)$(PREFIX)/lib/libcufetch-fmt.a ifeq ($(GUI_APP), 1) mkdir -p $(DESTDIR)$(APPPREFIX) cp -f $(NAME).desktop $(DESTDIR)$(APPPREFIX)/$(NAME).desktop @@ -134,15 +167,17 @@ uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(TARGET) $(DESTDIR)$(PREFIX)/bin/cufetch rm -f $(DESTDIR)$(MANPREFIX)/man1/$(NAME).1 rm -f $(DESTDIR)$(APPPREFIX)/$(NAME).desktop - rm -rf $(DESTDIR)$(PREFIX)/share/customfetch/ + rm -rf $(DESTDIR)$(PREFIX)/share/licenses/$(NAME)/ + rm -rf $(DESTDIR)$(PREFIX)/share/$(NAME)/ + rm -rf $(DESTDIR)$(PREFIX)/include/cufetch/ + rm -rf $(DESTDIR)$(ICONPREFIX)/$(NAME)/ + find $(DESTDIR)$(LOCALEDIR) -type f -path "$(DESTDIR)$(LOCALEDIR)/*/LC_MESSAGES/$(NAME).mo" -exec rm -f {} \; remove: uninstall delete: uninstall updatever: sed -i "s#$(OLDVERSION)#$(VERSION)#g" $(wildcard .github/workflows/*.yml) compile_flags.txt - sed -i "s#versionName = \"$(OLDVERSION)\"#versionName = \"$(VERSION)\"#g" android/app/build.gradle.kts - sed -i "s#set(VERSION \"$(OLDVERSION)\")#set(VERSION \"$(VERSION)\")#g" CMakeLists.txt android/CMakeLists.txt - sed -i "s#Project-Id-Version: customfetch $(OLDVERSION)#Project-Id-Version: customfetch $(VERSION)#g" po/* + sed -i "s#Project-Id-Version: $(NAME) $(OLDVERSION)#Project-Id-Version: $(NAME) $(VERSION)#g" po/* -.PHONY: $(TARGET) updatever remove uninstall delete dist distclean fmt toml install all locale +.PHONY: $(TARGET) updatever remove uninstall delete dist distclean fmt toml libcufetch install all locale diff --git a/README.md b/README.md index 39a843c5..a0f45b54 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,11 @@ -

- Customfetch -

-

- A system information fetch tool (or neofetch like program), which its focus point is the performance and customizability + +

+

+

+ A modular information fetching (neofetch-like) tool, which its focus point is the performance and customizability

+

@@ -30,24 +31,18 @@

-

- It's even an android widget and GTK3 app -

- - - -

- -

## Key Features -* Run customfetch as a **terminal** or **GTK3 application** or even as an **android widget** -* Really easy customizable and fast, check [Config (with explanation)](#config-with-explanation) section -* Super lightweight, 3.3MB max +* Run customfetch as a **terminal** or **GTK3 application** or even as an [android widget](https://github.com/Toni500github/customfetch-android-app) +* Really easy to [customize](#How-to-customize) +* Incredibly extensible information fetchings via external plugins +* Super lightweight, 3.3MB max (GTK3 application) + +# Dependencies +Customfetch requires **no mandatory** dependencies.\ +For building from source, only requires **C++20** but it's possible to compile with C++17 too (may not be always supported) -# Depends -Customfetch only requires the standard C++ library from `gcc-libs`, which should come already pre-installed in almost all distros. Other than that, customfetch requires **no mandatory** dependencies.\ -Currently requires **C++20**, but it's possible to compile with C++17 too (may not be always supported)\ +Here I'll list some packages to install for the GTK3 app or for making customfetch faster. The names of the packages can vary depending on the package manager or distro. If you want to install the GUI app install from your package manager: @@ -65,8 +60,7 @@ optional dependencies that will make customfetch faster. Download the latest `.deb` package in [releases](https://github.com/Toni500github/customfetch/releases/latest) ## Android widget -Download the latest `.apk` file in the [releases](https://github.com/Toni500github/customfetch/releases/latest) \ -AFAIK The google play protect doesn't detect any malware. If your antivirus does, please open an [issue here](https://github.com/Toni500github/customfetch/issues) +Moved to the new repo: https://github.com/Toni500github/customfetch-android-app ## Arch and based (AUR) ```bash @@ -79,7 +73,7 @@ yay -S customfetch-gui-bin ## General Distros (Manual installation) Download the latest `.tar.gz` tarball file in [releases](https://github.com/Toni500github/customfetch/releases/latest) \ -It contains the `/usr` directory where you'll install it in your distro. Useful for package managers too +It contains the `/usr` directory where you'll install it in your distro. Useful for package managers too. ## Arch and based (AUR) (source) ```bash @@ -99,22 +93,10 @@ yay -S customfetch-git yay -S customfetch-gui-git ``` -## Android widget app (unstable) -Download the latest apk build from the [GitHub actions](https://github.com/Toni500github/customfetch/actions/workflows/makefile.yml) artifacts\ -If you can't download or you are not logged in GitHub, use this link: https://nightly.link/Toni500github/customfetch/workflows/makefile/main/customfetch-android-app.zip - -## Android widget app (source) -```bash -# need java 17 + gradle 8.9 installed. -# It's suggested to build from android studio, -# so you that you can install the NDK library -./android/gradlew assembleDebug --project-dir=./android -``` - ## Compile from (source) (unstable) ```bash # clone the git dir -git clone https://github.com/Toni500github/customfetch +git clone --depth=1 https://github.com/Toni500github/customfetch cd customfetch # DEBUG=0 for release build @@ -125,7 +107,7 @@ make install DEBUG=0 GUI_APP=0 customfetch ``` -## Config (with explanation) +## How to customize Read the manual `customfetch.1` or execute customfetch with the arg `-w` for knowing more about the configuration in customfetch.\ This is only an explaination about tags and preview, that can be always found in the documentation. @@ -140,7 +122,7 @@ Here's an example using my config # The array for displaying the system infos layout = [ "$", - "$<title_sep>", + "$<title.sep>", "${auto}OS: $<os.name> $<system.arch>", "${auto}Host: $<system.host>", "${auto}Kernel: $<os.kernel>", @@ -148,12 +130,12 @@ layout = [ "${auto}Terminal: $<user.terminal>", "${auto}Shell: $<user.shell>", "${auto}Packages: $<os.pkgs>", - "${auto}Theme: $<theme-gtk-all.name>", - "${auto}Icons: $<theme-gtk-all.icons>", - "${auto}Font: $<theme-gtk-all.font>", + "${auto}Theme: $<theme.gtk.all.name>", + "${auto}Icons: $<theme.gtk.all.icons>", + "${auto}Font: $<theme.gtk.all.font>", "${auto}Cursor: $<theme.cursor>", - "${auto}WM: $<user.wm_name> $<user.wm_version>", - "${auto}DE: $<user.de_name> $<user.de_version>", + "${auto}WM: $<user.wm.name> $<user.wm.version>", + "${auto}DE: $<user.de.name> $<user.de.version>", "$<auto.disk>", "${auto}Swap: $<swap>", "${auto}CPU: $<cpu>", @@ -161,7 +143,7 @@ layout = [ "${auto}RAM: $<ram>", "", "$<colors>", # normal colors palette - "$<colors_light>" # light colors palette + "$<colors.light>" # light colors palette ] @@ -169,16 +151,16 @@ layout = [ In the config we got an array variable called "layout". That's the variable where you customize how the infos should be displayed.\ There are 5 tags: -* `$<module.member>` - Used for printing the system info value of a member of a module. -* `${color}` - Used for displaying text in a specific color. +* `$<info.module>` - Used for printing the value of a module or its submembers. +* `${color}` - Used for displaying text in a specific color after it. * `$(bash command)` - Used to execute bash commands and print the output. * `$[something,equalToSomethingElse,iftrue,ifalse]` - Conditional tag to display different outputs based on the comparison. * `$%n1,n2%` - Used to print the percentage and print with colors They can be used in the ascii art text file and layout, but how to use them? -* **The info tag (`$<>`)** will print a value of a member of a module\ - e.g `$<user.name>` will print the username, `$<os.kernel_version>` will print the kernel version and so on.\ +* **The info tag (`$<>`)** will print a value of a member of a module.\ + e.g `$<user.name>` will print the username, `$<os.kernel.version>` will print the kernel version and so on.\ All the modules and their members are listed in the `--list-modules` argument * **The bash command tag (`$()`)** let's you execute bash commands and print the output\ @@ -232,11 +214,22 @@ They can be used in the ascii art text file and layout, but how to use them? Any `$` or brackets can be escaped with a backslash `\`. You need to escape backslashes too :(\ **NOTE:** For having compatibility with the GUI app, you need to escape `<` (EXCEPT if you are using in a info tag, like `$<os.name>`) and `&`\ -e.g `the number 50 is \< than 100 \& 98` -Won't affect the printing in terminal +e.g `the number 50 is \\< than 100 \\&\\& 98` +Won't affect the printing in terminal. + +## Star History + +<a href="https://www.star-history.com/#Toni500github/customfetch&Date"> + <picture> + <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=Toni500github/customfetch&type=Date&theme=dark" /> + <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=Toni500github/customfetch&type=Date" /> + <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=Toni500github/customfetch&type=Date" /> + </picture> +</a> # TODOs -* release v1.0.0 +* release v2.0.0 +* work on the android app (later) # Thanks I would like to thanks: @@ -244,6 +237,9 @@ I would like to thanks:       For helping me initialize this project and motivate me to keep going\       And also for making my customizability idea come true with the first prototype of the parser. +* [saberr26](https://github.com/saberr26), \ +      For making the project logos + * the Better C++ [discord server](https://discord.gg/uSzTjkXtAM), \       For helping me improving the codebase and helping me with any issues I got,\       And also for being patient with me XD diff --git a/android/.gitignore b/android/.gitignore deleted file mode 100644 index aa724b77..00000000 --- a/android/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -*.iml -.gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures -.externalNativeBuild -.cxx -local.properties diff --git a/android/.idea/.gitignore b/android/.idea/.gitignore deleted file mode 100644 index 26d33521..00000000 --- a/android/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/android/.idea/.name b/android/.idea/.name deleted file mode 100644 index f42fe1a8..00000000 --- a/android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -customfetch_android \ No newline at end of file diff --git a/android/.idea/codeStyles/Project.xml b/android/.idea/codeStyles/Project.xml deleted file mode 100644 index a27d1e9c..00000000 --- a/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,126 +0,0 @@ -<component name="ProjectCodeStyleConfiguration"> - <code_scheme name="Project" version="173"> - <JetCodeStyleSettings> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - </JetCodeStyleSettings> - <clangFormatSettings> - <option name="ENABLED" value="true" /> - </clangFormatSettings> - <codeStyleSettings language="XML"> - <option name="FORCE_REARRANGE_MODE" value="1" /> - <indentOptions> - <option name="CONTINUATION_INDENT_SIZE" value="4" /> - </indentOptions> - <arrangement> - <rules> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:android</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>xmlns:.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:id</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*:name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>name</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>style</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>^$</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE> - </AND> - </match> - <order>ANDROID_ATTRIBUTE_ORDER</order> - </rule> - </section> - <section> - <rule> - <match> - <AND> - <NAME>.*</NAME> - <XML_ATTRIBUTE /> - <XML_NAMESPACE>.*</XML_NAMESPACE> - </AND> - </match> - <order>BY_NAME</order> - </rule> - </section> - </rules> - </arrangement> - </codeStyleSettings> - <codeStyleSettings language="kotlin"> - <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> - </codeStyleSettings> - </code_scheme> -</component> \ No newline at end of file diff --git a/android/.idea/codeStyles/codeStyleConfig.xml b/android/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c..00000000 --- a/android/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ -<component name="ProjectCodeStyleConfiguration"> - <state> - <option name="USE_PER_PROJECT_SETTINGS" value="true" /> - </state> -</component> \ No newline at end of file diff --git a/android/.idea/compiler.xml b/android/.idea/compiler.xml deleted file mode 100644 index b86273d9..00000000 --- a/android/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="CompilerConfiguration"> - <bytecodeTargetLevel target="21" /> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/deploymentTargetSelector.xml b/android/.idea/deploymentTargetSelector.xml deleted file mode 100644 index b268ef36..00000000 --- a/android/.idea/deploymentTargetSelector.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="deploymentTargetSelector"> - <selectionStates> - <SelectionState runConfigName="app"> - <option name="selectionMode" value="DROPDOWN" /> - </SelectionState> - </selectionStates> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/gradle.xml b/android/.idea/gradle.xml deleted file mode 100644 index 7b3006b6..00000000 --- a/android/.idea/gradle.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="GradleMigrationSettings" migrationVersion="1" /> - <component name="GradleSettings"> - <option name="linkedExternalProjectsSettings"> - <GradleProjectSettings> - <option name="testRunner" value="CHOOSE_PER_TEST" /> - <option name="externalProjectPath" value="$PROJECT_DIR$" /> - <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> - <option name="modules"> - <set> - <option value="$PROJECT_DIR$" /> - <option value="$PROJECT_DIR$/app" /> - </set> - </option> - <option name="resolveExternalAnnotations" value="false" /> - </GradleProjectSettings> - </option> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/kotlinc.xml b/android/.idea/kotlinc.xml deleted file mode 100644 index 148fdd24..00000000 --- a/android/.idea/kotlinc.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="KotlinJpsPluginSettings"> - <option name="version" value="1.9.24" /> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/migrations.xml b/android/.idea/migrations.xml deleted file mode 100644 index f8051a6f..00000000 --- a/android/.idea/migrations.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="ProjectMigrations"> - <option name="MigrateToGradleLocalJavaHome"> - <set> - <option value="$PROJECT_DIR$" /> - </set> - </option> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/misc.xml b/android/.idea/misc.xml deleted file mode 100644 index b2c751a3..00000000 --- a/android/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ -<project version="4"> - <component name="ExternalStorageConfigurationManager" enabled="true" /> - <component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK"> - <output url="file://$PROJECT_DIR$/build/classes" /> - </component> - <component name="ProjectType"> - <option name="id" value="Android" /> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/runConfigurations.xml b/android/.idea/runConfigurations.xml deleted file mode 100644 index 16660f1d..00000000 --- a/android/.idea/runConfigurations.xml +++ /dev/null @@ -1,17 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="RunConfigurationProducerService"> - <option name="ignoredProducers"> - <set> - <option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" /> - <option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" /> - <option value="com.intellij.execution.junit.PatternConfigurationProducer" /> - <option value="com.intellij.execution.junit.TestInClassConfigurationProducer" /> - <option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" /> - <option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" /> - <option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" /> - <option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" /> - </set> - </option> - </component> -</project> \ No newline at end of file diff --git a/android/.idea/vcs.xml b/android/.idea/vcs.xml deleted file mode 100644 index 6c0b8635..00000000 --- a/android/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="VcsDirectoryMappings"> - <mapping directory="$PROJECT_DIR$/.." vcs="Git" /> - </component> -</project> \ No newline at end of file diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt deleted file mode 100644 index 75319c82..00000000 --- a/android/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -# Same file as the one before, just used to be compiled for android - -cmake_minimum_required(VERSION 3.10) - -project(customfetch) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -ggdb3 -O0 -DDEBUG=1 -Wall -Wextra -Wpedantic -Wno-unused-parameter") - -include_directories(${CMAKE_SOURCE_DIR}/../include) - -file(GLOB - SRC - "${CMAKE_SOURCE_DIR}/../src/*.cpp" - "${CMAKE_SOURCE_DIR}/../src/query/linux/*.cpp" - "${CMAKE_SOURCE_DIR}/../src/query/android/*.cpp" - "${CMAKE_SOURCE_DIR}/../src/query/linux/utils/*.cpp" - "${CMAKE_SOURCE_DIR}/../src/toml++/toml.cpp" - "app/src/main/cpp/customfetch_android.cpp" -) - -add_library(customfetch SHARED ${SRC}) - -# get git branch -execute_process( - COMMAND git rev-parse --abbrev-ref HEAD - OUTPUT_VARIABLE GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE -) - -set(VERSION "1.0.0") -target_compile_definitions(customfetch PRIVATE - VERSION="${VERSION}" - BRANCH="${GIT_BRANCH}" - DANDROID_APP=1 -) - -# fmt -add_library(fmt STATIC - "${CMAKE_SOURCE_DIR}/../src/fmt/os.cc" - "${CMAKE_SOURCE_DIR}/../src/fmt/format.cc") - -# add library -target_link_libraries(customfetch fmt log android) diff --git a/android/app/.gitignore b/android/app/.gitignore deleted file mode 100644 index 42afabfd..00000000 --- a/android/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts deleted file mode 100644 index 7612ee44..00000000 --- a/android/app/build.gradle.kts +++ /dev/null @@ -1,108 +0,0 @@ -import java.io.ByteArrayOutputStream -import java.util.Properties - -plugins { - alias(libs.plugins.android.application) - alias(libs.plugins.kotlin.android) -} - -fun getGitCommitHash(): String { - return try { - val stdout = ByteArrayOutputStream() - exec { - commandLine("git", "rev-parse", "--short", "HEAD") - standardOutput = stdout - } - stdout.toString().trim() - } catch (e: Exception) { - "unknown" - } -} - -android { - namespace = "org.toni.customfetch_android" - compileSdk = 35 - - defaultConfig { - applicationId = "org.toni.customfetch_android" - minSdk = 26 - targetSdk = 35 - versionCode = 1 - versionName = "1.0.0" - - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - externalNativeBuild { - cmake { - cppFlags += "-I${rootDir}/../include -DANDROID_APP=1" - targets("customfetch") - } - } - } - - signingConfigs { - create("release") { - val propertiesFile = rootProject.file("signing.properties") - if (propertiesFile.exists()) { - val properties = Properties().apply { - load(propertiesFile.reader()) - } - storeFile = File(properties.getProperty("storeFilePath")) - storePassword = properties.getProperty("storePassword") - keyPassword = properties.getProperty("keyPassword") - keyAlias = properties.getProperty("keyAlias") - } - } - } - - buildTypes { - getByName("debug") { - buildConfigField("String", "GIT_COMMIT_HASH", "\"${getGitCommitHash()}\"") - } - getByName("release") { - buildConfigField("String", "GIT_COMMIT_HASH", "\"${getGitCommitHash()}\"") - } - release { - getByName("release") { - signingConfig = signingConfigs.getByName("release") - } - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - kotlinOptions { - jvmTarget = "11" - } - buildFeatures { - viewBinding = true - buildConfig = true - } - externalNativeBuild { - cmake { - path = file("../CMakeLists.txt") - version = "3.22.1" - } - } -} - -dependencies { - implementation(libs.colorpickerview) - implementation(libs.colorpickerpref) - implementation(libs.expandablelayout) - - implementation(libs.androidx.core.ktx) - implementation(libs.androidx.appcompat) - implementation(libs.material) - implementation(libs.androidx.constraintlayout) - implementation(libs.androidx.navigation.fragment.ktx) - implementation(libs.androidx.navigation.ui.ktx) - implementation(libs.androidx.preference.ktx) - implementation(libs.androidx.preference) -} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro deleted file mode 100644 index 481bb434..00000000 --- a/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index 31fbd2e7..00000000 --- a/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools"> - - <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> - <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> - - <application - android:allowBackup="true" - android:dataExtractionRules="@xml/data_extraction_rules" - android:fullBackupContent="@xml/backup_rules" - android:icon="@mipmap/ic_launcher" - android:label="@string/app_name" - android:roundIcon="@mipmap/ic_launcher_round" - android:supportsRtl="true" - android:theme="@style/Theme.MainActivity" - tools:targetApi="31"> - <receiver - android:name=".widget.Customfetch" - android:exported="false"> - <intent-filter> - <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> - </intent-filter> - - <meta-data - android:name="android.appwidget.provider" - android:resource="@xml/customfetch_info" /> - </receiver> - - <activity - android:name=".widget.CustomfetchConfigureActivity" - android:exported="false"> - <intent-filter> - <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> - </intent-filter> - </activity> - <activity - android:name=".MainActivity" - android:exported="true"> - <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> - </intent-filter> - - <meta-data - android:name="android.app.lib_name" - android:value="" /> - </activity> - </application> - -</manifest> \ No newline at end of file diff --git a/android/app/src/main/assets b/android/app/src/main/assets deleted file mode 120000 index 2b56518e..00000000 --- a/android/app/src/main/assets +++ /dev/null @@ -1 +0,0 @@ -../../../../assets \ No newline at end of file diff --git a/android/app/src/main/cpp/customfetch_android.cpp b/android/app/src/main/cpp/customfetch_android.cpp deleted file mode 100644 index 6bc4ad4d..00000000 --- a/android/app/src/main/cpp/customfetch_android.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include <jni.h> -#include <string> -#include <sstream> -#include "util.hpp" - -#define ARRAY_SIZE(x) sizeof(x) / sizeof(x[0]) - -extern std::string mainAndroid_and_render(int argc, char *argv[], JNIEnv *env, jobject obj, bool do_not_load_config); - -extern "C" JNIEXPORT jstring JNICALL -Java_org_toni_customfetch_1android_widget_CustomfetchMainRender_mainAndroid(JNIEnv *env, jobject obj, jstring args, jboolean do_not_load_config) { - const std::string& str_args = env->GetStringUTFChars(args, nullptr); - const std::vector<std::string>& tokens = split(str_args, ' '); - char *argv[tokens.size()]; - for (size_t i = 0; i < tokens.size(); ++i) - argv[i] = strdup(tokens[i].c_str()); - - return env->NewStringUTF(mainAndroid_and_render(ARRAY_SIZE(argv), argv, env, obj, do_not_load_config).c_str()); -} \ No newline at end of file diff --git a/android/app/src/main/java/org/toni/customfetch_android/AboutMeFragment.kt b/android/app/src/main/java/org/toni/customfetch_android/AboutMeFragment.kt deleted file mode 100644 index 6cf2111e..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/AboutMeFragment.kt +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android - -import android.os.Bundle -import android.text.method.LinkMovementMethod -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import org.toni.customfetch_android.databinding.AboutMeFragmentBinding - -class AboutMeFragment : Fragment() { - - private var _binding: AboutMeFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = AboutMeFragmentBinding.inflate(inflater, container, false) - binding.toolbarAboutMe.apply { - setNavigationIcon(R.drawable.arrow_back) - setNavigationOnClickListener { _ -> - requireActivity().supportFragmentManager.popBackStack() - } - } - - binding.burntGithubLink.movementMethod = LinkMovementMethod.getInstance() - binding.toniGithubLink.movementMethod = LinkMovementMethod.getInstance() - binding.bcppDiscordLink.movementMethod = LinkMovementMethod.getInstance() - binding.fmtlibGithubLink.movementMethod = LinkMovementMethod.getInstance() - binding.tomlLibGithubLink.movementMethod = LinkMovementMethod.getInstance() - binding.colorpickerviewGithubLink.movementMethod = LinkMovementMethod.getInstance() - binding.quartzitechunkGithubLink.movementMethod = LinkMovementMethod.getInstance() - - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/MainActivity.kt b/android/app/src/main/java/org/toni/customfetch_android/MainActivity.kt deleted file mode 100644 index 79b26f6a..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/MainActivity.kt +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android - -import android.animation.ArgbEvaluator -import android.animation.ValueAnimator -import android.annotation.SuppressLint -import android.content.Context -import android.content.Intent -import android.content.res.AssetManager -import android.graphics.drawable.GradientDrawable -import android.net.Uri -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.provider.Settings -import android.util.Log -import android.view.MotionEvent -import android.view.View -import android.view.animation.AccelerateDecelerateInterpolator -import android.view.animation.AnimationUtils -import android.widget.LinearLayout -import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.preference.PreferenceManager -import org.toni.customfetch_android.databinding.ActivityMainBinding -import java.io.File -import java.io.FileOutputStream -import java.io.IOException -import java.nio.file.Files -import kotlin.io.path.Path - -// kinda magic numbers -const val TEST_CONFIG_FILE_RC = 6969 - -class MainActivity : AppCompatActivity() { - private lateinit var binding: ActivityMainBinding - - @SuppressLint("ClickableViewAccessibility", "SetTextI18n") - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) - setContentView(binding.root) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (!Environment.isExternalStorageManager()) { - val alert = AlertDialog.Builder(this) - .setTitle("Grant external storage management permission") - .setMessage("Customfetch needs permissions to manage external storage to able to access config files.\n"+ - "By default we going to read/write in the following directories:\n"+ - "/storage/emulated/0/.config/\n"+ - "/storage/emulated/0/.config/customfetch/") - .setPositiveButton("Grant permission" - ) { _, _ -> - val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) - startActivity(intent) - } - .setIcon(R.drawable.icon_alert_yellow) - - val view = layoutInflater.inflate(R.layout.grant_perm, null, false) - alert.setView(view) - alert.show() - } - } - - if (!Files.exists(Path(filesDir.absolutePath + "/ascii"))) - copyToAssetFolder(assets, filesDir.absolutePath, "ascii") - - // warn to user to generate the default widget configs - // else during the widget configuration, it will kinda fuckup some things - if (!Files.exists(Path(filesDir.absolutePath + "/../shared_prefs/org.toni.customfetch_android_preferences.xml"))) - binding.clickMeSettings.visibility = View.VISIBLE - - binding.testConfigFile.setOnTouchListener { view, event -> startAnimation(view, event) } - binding.testConfigFile.setOnClickListener { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "*/*" - } - startActivityForResult(intent, TEST_CONFIG_FILE_RC) - } - - binding.aboutMe.setOnTouchListener { view, event -> startAnimation(view, event) } - binding.aboutMe.setOnClickListener { setFragment(AboutMeFragment()) } - - binding.widgetSettings.setOnTouchListener { view, event -> startAnimation(view, event) } - binding.widgetSettings.setOnClickListener { - setFragment(SettingsFragment()) - binding.clickMeSettings.visibility = View.GONE - } - - setViewBgColor(binding.joinDiscordLink, 0XFF5865F2.toInt()) - binding.joinDiscordLink.setOnTouchListener { view, event -> startAnimation(view, event, true)} - binding.joinDiscordLink.setOnClickListener { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://discord.gg/dcD7d3Qfus"))) - } - - setViewBgColor(binding.joinCustomfetchReddit, 0XFFFF4500.toInt()) - binding.joinCustomfetchReddit.setOnTouchListener { view, event -> startAnimation(view, event, true)} - binding.joinCustomfetchReddit.setOnClickListener { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://reddit.com/r/customfetch"))) - } - - setViewBgColor(binding.starGithubRepo, 0XFF010409.toInt()) - binding.starGithubRepo.setOnTouchListener { view, event -> startAnimation(view, event, true)} - binding.starGithubRepo.setOnClickListener { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://github.com/Toni500github/customfetch"))) - } - - binding.appVersion.text = "customfetch v${BuildConfig.VERSION_NAME} (${BuildConfig.GIT_COMMIT_HASH}) (${BuildConfig.BUILD_TYPE})" - - binding.collapseSocialsTitle.setOnClickListener { - if (binding.collapseSocialsLayout.isExpanded) { - binding.arrowIcon.animate().rotation(0f).setInterpolator(AccelerateDecelerateInterpolator()).start() - binding.collapseSocialsLayout.collapse() - } else { - binding.arrowIcon.animate().rotation(180f).setInterpolator(AccelerateDecelerateInterpolator()).start() - binding.collapseSocialsLayout.expand() - } - } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - super.onActivityResult(requestCode, resultCode, data) - if (resultCode == RESULT_OK && data != null && data.data != null) { - data.data?.let { uri -> - when (requestCode) { - TEST_CONFIG_FILE_RC -> { - val fragment = TestConfigFragment().apply { - configFile = PathUtil.getPath(this@MainActivity, uri) - } - setFragment(fragment, android.R.anim.slide_in_left) - } - else -> {} - } - } - } - } - - private fun setFragment(fragment: Fragment, slideInAnim: Int = R.anim.slide_in) { - supportFragmentManager.beginTransaction() - .setCustomAnimations( - slideInAnim, // enter - android.R.animator.fade_out, // exit - android.R.animator.fade_in, // popEnter - R.anim.slide_out // popExit - ) - .replace(android.R.id.content, fragment) - .addToBackStack(null).commit() - } - - private fun setViewBgColor(view: View, color: Int) { - val drawable = view.background as GradientDrawable - drawable.setColor(color) - } - - private fun startAnimation(view: View, event: MotionEvent, scaleAnimation: Boolean = false): Boolean { - if (scaleAnimation) { - val animRes = when (event.action) { - MotionEvent.ACTION_DOWN -> R.anim.scale_down - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> R.anim.scale_up - else -> return false - } - - val animation = AnimationUtils.loadAnimation(this, animRes) - view.startAnimation(animation) - return false - } - - val drawable = view.background as GradientDrawable - val colorAnimator = when (event.action) { - MotionEvent.ACTION_DOWN -> { - ValueAnimator.ofObject(ArgbEvaluator(), getColor(R.color.buttonBg), - getColor(R.color.reverseButtonBg)) - } - MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { - ValueAnimator.ofObject(ArgbEvaluator(), getColor(R.color.reverseButtonBg), - getColor(R.color.buttonBg)) - } - else -> ValueAnimator() - } - colorAnimator.duration = 300 - colorAnimator.addUpdateListener { animator -> - drawable.setColor(animator.animatedValue as Int) - } - colorAnimator.start() - return false - } -} - -internal fun copyToAssetFolder(assets: AssetManager, absolutePath: String, assetSubFolder: String) { - try { - copyDirectory(assets, assetSubFolder, absolutePath) - Log.d("AssetCopy", "All files copied to: $absolutePath") - } catch (e: IOException) { - Log.e("AssetCopy", "Failed to copy asset folder: $assetSubFolder", e) - } -} - -internal fun getAppSettingsPrefString(context: Context, name: String): String { - val sharedPref = PreferenceManager.getDefaultSharedPreferences(context) - val str = sharedPref.getString(name, null) ?: "" - return str -} - -internal fun getAppSettingsPrefInt(context: Context, name: String): Int { - val sharedPref = PreferenceManager.getDefaultSharedPreferences(context) - return sharedPref.getInt(name, 0) -} - -internal fun getAppSettingsPrefBool(context: Context, name: String): Boolean { - val sharedPref = PreferenceManager.getDefaultSharedPreferences(context) - return sharedPref.getBoolean(name, false) -} - -@Throws(IOException::class) -private fun copyDirectory( - assetManager: AssetManager, - sourceDir: String, - destinationDir: String -) { - val files = assetManager.list(sourceDir) - if (files.isNullOrEmpty()) - return - - val destDir = File(destinationDir, sourceDir) - if (!destDir.exists() && !destDir.mkdirs()) - throw IOException("Failed to create directory: " + destDir.absolutePath) - - for (fileName in files) { - val assetPath = "$sourceDir/$fileName" - val destPath = destDir.path + "/" + fileName - if (isDirectory(assetManager, assetPath)) - copyDirectory(assetManager, assetPath, destinationDir) - else - copyFile(assetManager, assetPath, destPath) - } -} - -@Throws(IOException::class) -private fun isDirectory(assetManager: AssetManager, path: String): Boolean = - !assetManager.list(path).isNullOrEmpty() - -@Throws(IOException::class) -private fun copyFile(assetManager: AssetManager, assetPath: String, destPath: String) { - assetManager.open(assetPath).use { `in` -> - FileOutputStream(destPath).use { out -> - val buffer = ByteArray(8192) - var bytesRead: Int - while ((`in`.read(buffer).also { bytesRead = it }) != -1) - out.write(buffer, 0, bytesRead) - - Log.d("AssetCopy", "File copied: $destPath") - } - } -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/PathUtil.java b/android/app/src/main/java/org/toni/customfetch_android/PathUtil.java deleted file mode 100644 index 1b47d064..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/PathUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.toni.customfetch_android; - -import android.annotation.SuppressLint; -import android.content.ContentUris; -import android.content.Context; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Environment; -import android.provider.DocumentsContract; -import android.provider.MediaStore; - -import java.net.URISyntaxException; - -/** - * Created by Aki on 1/7/2017. - * <a href="https://stackoverflow.com/a/41520090">original</a> - */ - -public class PathUtil { - /* - * Gets the file path of the given Uri. - */ - @SuppressLint("NewApi") - public static String getPath(Context context, Uri uri) throws URISyntaxException { - final boolean needToCheckUri = Build.VERSION.SDK_INT >= 19; - String selection = null; - String[] selectionArgs = null; - // Uri is different in versions after KITKAT (Android 4.4), we need to - // deal with different Uris. - if (needToCheckUri && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) { - if (isExternalStorageDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - return Environment.getExternalStorageDirectory() + "/" + split[1]; - } else if (isDownloadsDocument(uri)) { - final String id = DocumentsContract.getDocumentId(uri); - uri = ContentUris.withAppendedId( - Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); - } else if (isMediaDocument(uri)) { - final String docId = DocumentsContract.getDocumentId(uri); - final String[] split = docId.split(":"); - final String type = split[0]; - if ("image".equals(type)) { - uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; - } else if ("video".equals(type)) { - uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - } else if ("audio".equals(type)) { - uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; - } - selection = "_id=?"; - selectionArgs = new String[]{ split[1] }; - } - } - if ("content".equalsIgnoreCase(uri.getScheme())) { - String[] projection = { MediaStore.Images.Media.DATA }; - Cursor cursor = null; - try { - cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); - assert cursor != null; - int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); - if (cursor.moveToFirst()) { - return cursor.getString(column_index); - } - cursor.close(); - } catch (Exception e) { - } - } else if ("file".equalsIgnoreCase(uri.getScheme())) { - return uri.getPath(); - } - return null; - } - - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is ExternalStorageProvider. - */ - public static boolean isExternalStorageDocument(Uri uri) { - return "com.android.externalstorage.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is DownloadsProvider. - */ - public static boolean isDownloadsDocument(Uri uri) { - return "com.android.providers.downloads.documents".equals(uri.getAuthority()); - } - - /** - * @param uri The Uri to check. - * @return Whether the Uri authority is MediaProvider. - */ - public static boolean isMediaDocument(Uri uri) { - return "com.android.providers.media.documents".equals(uri.getAuthority()); - } -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/SettingsFragment.kt b/android/app/src/main/java/org/toni/customfetch_android/SettingsFragment.kt deleted file mode 100644 index 121422be..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/SettingsFragment.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android - -import android.os.Bundle -import android.view.View -import androidx.appcompat.widget.Toolbar -import androidx.preference.ListPreference -import androidx.preference.PreferenceFragmentCompat -import com.jaredrummler.android.colorpicker.ColorPreferenceCompat - -class SettingsFragment : PreferenceFragmentCompat() { - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - view.findViewById<Toolbar>(R.id.toolbar_settings)?.let { - it.setNavigationIcon(R.drawable.arrow_back) - it.setNavigationOnClickListener { - requireActivity().supportFragmentManager.popBackStack() - } - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.root_preferences, rootKey) - - val defaultCustomColor = findPreference<ColorPreferenceCompat>("default_custom_color") - - val defaultBgColor = findPreference<ListPreference>("default_bg_color") - defaultCustomColor?.isEnabled = (defaultBgColor?.findIndexOfValue(defaultBgColor.value.toString()) == 2) - defaultBgColor?.setOnPreferenceChangeListener { _, newValue -> - defaultCustomColor?.isEnabled = (defaultBgColor.findIndexOfValue(newValue.toString()) == 2) - true - } - } -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/TestConfigFragment.kt b/android/app/src/main/java/org/toni/customfetch_android/TestConfigFragment.kt deleted file mode 100644 index 131f3e4a..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/TestConfigFragment.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android - -import android.graphics.Color -import android.os.Bundle -import android.text.TextPaint -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import org.toni.customfetch_android.databinding.TestConfigFragmentBinding -import org.toni.customfetch_android.widget.customfetchRender - - -class TestConfigFragment : Fragment() { - - private var _binding: TestConfigFragmentBinding? = null - // This property is only valid between onCreateView and - // onDestroyView. - private val binding get() = _binding!! - - var configFile = "" - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = TestConfigFragmentBinding.inflate(inflater, container, false) - binding.toolbar.apply { - setNavigationIcon(R.drawable.arrow_back) - setNavigationOnClickListener { _ -> - requireActivity().supportFragmentManager.popBackStack() - } - } - - val result = customfetchRender.getParsedContent( - AppCompatActivity(), - 0, - 0f, - false, - TextPaint(), - "-C $configFile -Nnm \$<os.name_id>", // this is the important thing - false - ) - - if (result.contentEquals("android")) { - binding.titleResult.setTextColor(Color.GREEN) - binding.titleResult.text = "SUCCESS" - binding.testConfigResult.text = "config file '$configFile' works!!" - } else { - binding.titleResult.setTextColor(Color.RED) - binding.titleResult.text = "FAILURE" - binding.testConfigResult.text = result - } - - return binding.root - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/widget/Customfetch.kt b/android/app/src/main/java/org/toni/customfetch_android/widget/Customfetch.kt deleted file mode 100644 index c2b137b2..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/widget/Customfetch.kt +++ /dev/null @@ -1,186 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android.widget - -import android.app.PendingIntent -import android.appwidget.AppWidgetManager -import android.appwidget.AppWidgetProvider -import android.content.Context -import android.content.Intent -import android.content.res.Configuration.ORIENTATION_PORTRAIT -import android.os.Bundle -import android.text.TextPaint -import android.util.Log -import android.widget.RemoteViews -import org.toni.customfetch_android.R - -const val WIDGET_CLICK_ACTION = "org.toni.customfetch_android.WIDGET_CLICK" - -/** - * Implementation of App Widget functionality. - * App Widget Configuration implemented in [CustomfetchConfigureActivity] - */ -class Customfetch : AppWidgetProvider() { - override fun onUpdate( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetIds: IntArray - ) { - // There may be multiple widgets active, so update all of them - for (appWidgetId in appWidgetIds) { - updateAppWidget(context, appWidgetManager, appWidgetId) - } - } - - override fun onDeleted(context: Context, appWidgetIds: IntArray) { - // When the user deletes the widget, delete the preference associated with it. - for (appWidgetId in appWidgetIds) { - deleteConfigPrefs(context, appWidgetId) - } - } - - override fun onReceive(context: Context?, intent: Intent?) { - super.onReceive(context, intent) - Log.d("onReceiveTest", "intent.action = ${intent?.action}") - if (context != null && intent?.action == WIDGET_CLICK_ACTION) { - val appWidgetId = intent.getIntExtra( - AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID - ) - - if (appWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID) { - Log.d("onReceiveTest", "Widget clicked!") - updateAppWidget(context, AppWidgetManager.getInstance(context), appWidgetId) - } - } - } - - override fun onAppWidgetOptionsChanged( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetId: Int, - newOptions: Bundle - ) { - updateAppWidget(context, appWidgetManager, appWidgetId) - } -} - -// https://stackoverflow.com/a/58501760 -class WidgetSizeProvider( - private val context: Context // Do not pass Application context -) { - - private val appWidgetManager = AppWidgetManager.getInstance(context) - - fun getWidgetsSize(widgetId: Int): Pair<Int, Int> { - val isPortrait = (context.resources.configuration.orientation == ORIENTATION_PORTRAIT) - val width = getWidgetWidth(isPortrait, widgetId) - val height = getWidgetHeight(isPortrait, widgetId) - val widthInPx = context.dip(width) - val heightInPx = context.dip(height) - return widthInPx to heightInPx - } - - private fun getWidgetWidth(isPortrait: Boolean, widgetId: Int): Int = - if (isPortrait) { - getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) - } else { - getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH) - } - - private fun getWidgetHeight(isPortrait: Boolean, widgetId: Int): Int = - if (isPortrait) { - getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT) - } else { - getWidgetSizeInDp(widgetId, AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) - } - - private fun getWidgetSizeInDp(widgetId: Int, key: String): Int = - appWidgetManager.getAppWidgetOptions(widgetId).getInt(key, 0) - - private fun Context.dip(value: Int): Int = (value * resources.displayMetrics.density).toInt() - -} - -internal fun updateAppWidget( - context: Context, - appWidgetManager: AppWidgetManager, - appWidgetId: Int -) { - // if we are configuring the widget, then don't update the non existent widget - if (!isWidgetConfigured(context, appWidgetId)) - return - - val truncateText = getTruncateText(context, appWidgetId) - val bgColor = getBgColor(context, appWidgetId) - val widgetTextColor = getWidgetTextColor(context, appWidgetId) - - // create a TextPaint to be used to measure text size - val textPaint = TextPaint() - val textSizeSp = 8f - val textSizePx = textSizeSp * context.resources.displayMetrics.scaledDensity - textPaint.textSize = textSizePx - - val additionalTruncateWidth = getTruncateWidthPref(context, appWidgetId).toFloat() - var width = WidgetSizeProvider(context).getWidgetsSize(appWidgetId).first.toFloat() - if (additionalTruncateWidth > 0.20) - width *= additionalTruncateWidth - - Log.d("widthTesting", "textSizePx = $textSizePx") - Log.d("widthTesting", "width = $width") - - val parsedContent = - customfetchRender.getParsedContent( - context, - appWidgetId, - width, - truncateText, - textPaint - ) - - // both needed for when touching the widget - val intent = Intent(context, Customfetch::class.java).apply { - action = WIDGET_CLICK_ACTION - putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - } - - val pendingIntent = PendingIntent.getBroadcast( - context, - appWidgetId, - intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - - // Construct the RemoteViews object - val views = RemoteViews(context.packageName, R.layout.customfetch) - views.setOnClickPendingIntent(R.id.widget_root, pendingIntent) - views.setTextColor(R.id.customfetch_text, widgetTextColor) - views.setTextViewText(R.id.customfetch_text, parsedContent) - views.setInt(R.id.widget_root, "setBackgroundColor", bgColor) - - // Instruct the widget manager to update the widget - appWidgetManager.updateAppWidget(appWidgetId, views) -} diff --git a/android/app/src/main/java/org/toni/customfetch_android/widget/CustomfetchConfigureActivity.kt b/android/app/src/main/java/org/toni/customfetch_android/widget/CustomfetchConfigureActivity.kt deleted file mode 100644 index fb7ef2aa..00000000 --- a/android/app/src/main/java/org/toni/customfetch_android/widget/CustomfetchConfigureActivity.kt +++ /dev/null @@ -1,378 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -package org.toni.customfetch_android.widget - -import android.annotation.SuppressLint -import android.app.Activity -import android.appwidget.AppWidgetManager -import android.content.Context -import android.content.Intent -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.text.Editable -import android.text.SpannableStringBuilder -import android.text.Spanned -import android.text.TextPaint -import android.text.TextUtils.TruncateAt -import android.text.TextUtils.ellipsize -import android.text.TextWatcher -import android.util.TypedValue -import android.view.View -import android.widget.LinearLayout -import android.widget.Toast -import androidx.core.graphics.toColorInt -import androidx.core.text.HtmlCompat -import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener -import org.toni.customfetch_android.R -import org.toni.customfetch_android.databinding.ColorpickerviewLayoutBinding -import org.toni.customfetch_android.databinding.CustomfetchConfigureBinding -import org.toni.customfetch_android.getAppSettingsPrefBool -import org.toni.customfetch_android.getAppSettingsPrefInt -import org.toni.customfetch_android.getAppSettingsPrefString -import java.io.File -import java.nio.file.Files -import kotlin.io.path.Path - -/** - * The configuration screen for the [Customfetch] AppWidget. - */ -class CustomfetchConfigureActivity : Activity() { - private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID - private lateinit var binding: CustomfetchConfigureBinding - private var onAddWidget = View.OnClickListener { - val context = this@CustomfetchConfigureActivity - - // When the button is clicked, store the string locally - saveConfigPrefs( - context, - appWidgetId, - binding.argumentsConfigure.text.toString(), - binding.additionalTruncateWidth.text.toString(), - binding.truncateText.isChecked, - bgColor, - widgetTextColor - ) - - // It is the responsibility of the configuration activity to update the app widget - val appWidgetManager = AppWidgetManager.getInstance(context) - updateAppWidget(context, appWidgetManager, appWidgetId) - - // Make sure we pass back the original appWidgetId - val resultValue = Intent() - resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) - setResult(RESULT_OK, resultValue) - finish() - } - - private var bgColor = 0 - private var widgetTextColor = 0xFF8F9099.toInt() - - public override fun onCreate(icicle: Bundle?) { - super.onCreate(icicle) - - // Set the result to CANCELED. This will cause the widget host to cancel - // out of the widget placement if the user presses the back button. - setResult(RESULT_CANCELED) - - binding = CustomfetchConfigureBinding.inflate(layoutInflater) - setContentView(binding.root) - - binding.addButton.setOnClickListener(onAddWidget) - - // Find the widget id from the intent. - val extras = intent.extras - if (extras != null) { - appWidgetId = extras.getInt( - AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID - ) - } - - // If this activity was started with an intent without an app widget ID, finish with an error. - if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { - finish() - return - } - - binding.argumentsConfigure.setText(getArgsPref(this@CustomfetchConfigureActivity, appWidgetId)) - binding.additionalTruncateWidth.setText(getAppSettingsPrefString(this, "additional_truncate")) - binding.truncateText.isChecked = getAppSettingsPrefBool(this, "always_truncate") - binding.docsHelp.text = customfetchRender.mainAndroid("customfetch --help", true) - - binding.btnArgsHelp.setOnClickListener { - binding.docsHelp.text = customfetchRender.mainAndroid("customfetch --help", true) - } - binding.btnConfigHelp.setOnClickListener { - binding.docsHelp.text = customfetchRender.mainAndroid("customfetch --how-it-works", true) - } - binding.btnModulesHelp.setOnClickListener { - binding.docsHelp.text = customfetchRender.mainAndroid("customfetch --list-modules", true) - } - binding.btnLogosList.setOnClickListener { - binding.docsHelp.text = customfetchRender.mainAndroid("customfetch ${binding.argumentsConfigure.text} --list-logos", true) - } - - // set everything of the radio buttons at first configuration from the app. - when (getAppSettingsPrefString(this, "default_bg_color")) { - "system_bg_color" -> { - binding.selectBgColor.check(R.id.radio_system_bg_color) - setSystemBgColor() - } - "transparent_bg" -> { - binding.selectBgColor.check(R.id.radio_transparent_bg) - bgColor = 0x00FFFFFF - } - "custom_bg_color" -> { - binding.selectBgColor.check(R.id.radio_custom_colors) - setColorPickerView(binding.customBgColorSelect, true) - } - } - - binding.selectTextColor.setOnCheckedChangeListener { _, checkedId -> - when (checkedId) { - R.id.radio_default_text_color -> { - binding.customTextColorSelect.visibility = View.GONE - widgetTextColor = 0xFF8F9099.toInt() - } - - R.id.radio_custom_text_color -> { - setColorPickerView(binding.customTextColorSelect, false) - } - } - } - - binding.selectBgColor.setOnCheckedChangeListener { _, checkedId -> - when (checkedId) { - R.id.radio_system_bg_color -> { - binding.customBgColorSelect.visibility = View.GONE - setSystemBgColor() - } - - R.id.radio_transparent_bg -> { - binding.customBgColorSelect.visibility = View.GONE - bgColor = 0x00FFFFFF - } - - R.id.radio_custom_colors -> { - setColorPickerView(binding.customBgColorSelect, true) - } - } - } - - markWidgetConfigured(this, appWidgetId) - } - - private fun setSystemBgColor() { - val typedValue = TypedValue() - this.theme.resolveAttribute(android.R.attr.colorBackground, typedValue, true) - this.bgColor = typedValue.data - } - - @SuppressLint("SetTextI18n", "ClickableViewAccessibility") - // why tf didn't kotlin add an option for making function parameters mutable??? - private fun setColorPickerView(cpvLayout: LinearLayout, isBgColor: Boolean) { - val binding = ColorpickerviewLayoutBinding.inflate(layoutInflater, cpvLayout, true) - cpvLayout.visibility = View.VISIBLE - - // disable scrolling when interacting with the color picker - binding.colorPickerView.setOnTouchListener { view, _ -> - view.parent.requestDisallowInterceptTouchEvent(true); false - } - binding.alphaSlideBar.setOnTouchListener { view, _ -> - view.parent.requestDisallowInterceptTouchEvent(true); false - } - binding.brightnessSlideBar.setOnTouchListener { view, _ -> - view.parent.requestDisallowInterceptTouchEvent(true); false - } - - val defaultColor = - if (isBgColor) - getAppSettingsPrefInt(this, "default_bg_custom_color") - else - getAppSettingsPrefInt(this, "default_widget_text_color") - - binding.colorPickerHex.setText("#"+ Integer.toHexString(defaultColor).uppercase()) - binding.colorPreview.setBackgroundColor(defaultColor) - binding.colorPickerView.setInitialColor(defaultColor) - - binding.colorPickerHex.addTextChangedListener(object : TextWatcher { - override fun afterTextChanged(s: Editable) { - val col = s.toString() - if (isValidHex(col)) - binding.colorPickerView.setInitialColor(col.toColorInt()) - } - override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {} - } ) - - binding.colorPickerView.setColorListener(ColorEnvelopeListener { envelope, fromUser -> - if (!binding.colorPickerHex.text.contentEquals("#"+envelope.hexCode) && fromUser) - binding.colorPickerHex.setText("#"+envelope.hexCode) - - binding.colorPreview.setBackgroundColor(envelope.color) - if (isBgColor) bgColor = envelope.color - else widgetTextColor = envelope.color - }) - - binding.colorPickerView.attachAlphaSlider(binding.alphaSlideBar) - binding.colorPickerView.attachBrightnessSlider(binding.brightnessSlideBar) - } -} - -class CustomfetchMainRender { - fun getParsedContent( - context: Context, - appWidgetId: Int, - width: Float, - truncateText: Boolean, - paint: TextPaint, - otherArguments: String = "", - postToast: Boolean = true, - doNotLoadConfig: Boolean = false - ): SpannableStringBuilder { - val parsedContent = SpannableStringBuilder() - val arguments = otherArguments.ifEmpty { - getArgsPref(context, appWidgetId) - } - val htmlContent = mainAndroid("customfetch $arguments", doNotLoadConfig) - - val errorFile = "/storage/emulated/0/.config/customfetch/error_log.txt" - val errorLock = "/storage/emulated/0/.config/customfetch/error.lock" - if (Files.exists(Path(errorLock))) { - val file = File(errorLock) - val error = file.bufferedReader().use { it.readText() } - if (postToast) { - val handler = Handler(Looper.getMainLooper()) - handler.post { - Toast.makeText(context, error, Toast.LENGTH_LONG).show() - } - handler.post { - Toast.makeText(context, "read error logs at $errorFile", Toast.LENGTH_LONG) - .show() - } - } - file.delete() - parsedContent.append("read error logs at $errorFile\n\n$error") - return parsedContent - } - - if (truncateText) { - val eachLine = htmlContent!!.split("<br>").map { it.trim() } - for (line in eachLine) { - var parsedLine = HtmlCompat.fromHtml(line, HtmlCompat.FROM_HTML_MODE_COMPACT) - parsedLine = - ellipsize(parsedLine, paint, width, TruncateAt.END) as Spanned - parsedContent.appendLine(parsedLine) - } - } else { - parsedContent.append(htmlContent?.let { - HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT) - }) - } - - return parsedContent - } - - external fun mainAndroid(argv: String, doNotLoadConfig: Boolean): String? - companion object { - init { - System.loadLibrary("customfetch") - } - } -} -val customfetchRender = CustomfetchMainRender() - -private const val PREFS_NAME = "org.toni.customfetch_android.customfetch" -private const val PREF_PREFIX_KEY = "appwidget_" - -// Save the preferences to the SharedPreferences object for this widget -internal fun saveConfigPrefs( - context: Context, - appWidgetId: Int, - args: String, - truncateWidth: String, - truncateText: Boolean, - bgColor: Int, - widgetTextColor: Int -) { - val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit() - prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_args", args) - prefs.putString(PREF_PREFIX_KEY + appWidgetId + "_truncateWidth", truncateWidth) - prefs.putBoolean(PREF_PREFIX_KEY + appWidgetId + "_truncateText", truncateText) - prefs.putInt(PREF_PREFIX_KEY + appWidgetId + "_widgetTextColor", widgetTextColor) - prefs.putInt(PREF_PREFIX_KEY + appWidgetId + "_bgColor", bgColor) - prefs.apply() -} - -internal fun getArgsPref(context: Context, appWidgetId: Int): String { - val prefs = context.getSharedPreferences(PREFS_NAME, 0) - val args = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_args", null) - return args ?: getAppSettingsPrefString(context, "default_args") -} - -internal fun getTruncateWidthPref(context: Context, appWidgetId: Int): String { - val prefs = context.getSharedPreferences(PREFS_NAME, 0) - val value = prefs.getString(PREF_PREFIX_KEY + appWidgetId + "_truncateWidth", null) - return value ?: "0" -} - -internal fun getTruncateText(context: Context, appWidgetId: Int): Boolean { - val prefs = context.getSharedPreferences(PREFS_NAME, 0) - return prefs.getBoolean(PREF_PREFIX_KEY + appWidgetId + "_truncateText", false) -} - -internal fun getBgColor(context: Context, appWidgetId: Int): Int { - val prefs = context.getSharedPreferences(PREFS_NAME, 0) - return prefs.getInt(PREF_PREFIX_KEY + appWidgetId + "_bgColor", 0) -} - -internal fun getWidgetTextColor(context: Context, appWidgetId: Int): Int { - val prefs = context.getSharedPreferences(PREFS_NAME, 0) - return prefs.getInt(PREF_PREFIX_KEY + appWidgetId + "_widgetTextColor", 0) -} - -internal fun deleteConfigPrefs(context: Context, appWidgetId: Int) { - val prefs = context.getSharedPreferences(PREFS_NAME, 0).edit() - prefs.remove(PREF_PREFIX_KEY + appWidgetId + "_args") - prefs.remove(PREF_PREFIX_KEY + appWidgetId + "_truncateText") - prefs.remove(PREF_PREFIX_KEY + appWidgetId + "_truncateWidth") - prefs.remove(PREF_PREFIX_KEY + appWidgetId + "_bgColor") - prefs.remove(PREF_PREFIX_KEY + appWidgetId + "_widgetTextColor") - prefs.apply() -} - -fun markWidgetConfigured(context: Context, appWidgetId: Int) { - val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - prefs.edit().putBoolean(PREF_PREFIX_KEY + appWidgetId + "_configuring", true).apply() -} - -fun isWidgetConfigured(context: Context, appWidgetId: Int): Boolean { - val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - return prefs.getBoolean(PREF_PREFIX_KEY + appWidgetId + "_configuring", false) -} - -internal fun isValidHex(color: String): Boolean = - color.matches("^#[0-9A-Fa-f]{8}$".toRegex()) \ No newline at end of file diff --git a/android/app/src/main/res/anim/scale_down.xml b/android/app/src/main/res/anim/scale_down.xml deleted file mode 100644 index ad6b52d9..00000000 --- a/android/app/src/main/res/anim/scale_down.xml +++ /dev/null @@ -1,9 +0,0 @@ -<scale xmlns:android="http://schemas.android.com/apk/res/android" - android:fromXScale="1.0" - android:toXScale="0.95" - android:fromYScale="1.0" - android:toYScale="0.95" - android:pivotX="50%" - android:pivotY="50%" - android:duration="50" - android:fillAfter="true" /> diff --git a/android/app/src/main/res/anim/scale_up.xml b/android/app/src/main/res/anim/scale_up.xml deleted file mode 100644 index 0c331263..00000000 --- a/android/app/src/main/res/anim/scale_up.xml +++ /dev/null @@ -1,9 +0,0 @@ -<scale xmlns:android="http://schemas.android.com/apk/res/android" - android:fromXScale="0.95" - android:toXScale="1.0" - android:fromYScale="0.95" - android:toYScale="1.0" - android:pivotX="50%" - android:pivotY="50%" - android:duration="150" - android:fillAfter="true" /> diff --git a/android/app/src/main/res/anim/slide_in.xml b/android/app/src/main/res/anim/slide_in.xml deleted file mode 100644 index 8a4a29c8..00000000 --- a/android/app/src/main/res/anim/slide_in.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<translate xmlns:android="http://schemas.android.com/apk/res/android" - android:duration="@android:integer/config_shortAnimTime" - android:interpolator="@android:anim/decelerate_interpolator" - android:fromXDelta="100%" - android:toXDelta="0%" /> \ No newline at end of file diff --git a/android/app/src/main/res/anim/slide_out.xml b/android/app/src/main/res/anim/slide_out.xml deleted file mode 100644 index 220ef3b9..00000000 --- a/android/app/src/main/res/anim/slide_out.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<translate xmlns:android="http://schemas.android.com/apk/res/android" - android:duration="@android:integer/config_shortAnimTime" - android:interpolator="@android:anim/decelerate_interpolator" - android:fromXDelta="0%" - android:toXDelta="100%" /> \ No newline at end of file diff --git a/android/app/src/main/res/drawable-night/github_mark.png b/android/app/src/main/res/drawable-night/github_mark.png deleted file mode 100644 index d66bdeb4..00000000 Binary files a/android/app/src/main/res/drawable-night/github_mark.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-night/grant_perm.png b/android/app/src/main/res/drawable-night/grant_perm.png deleted file mode 100644 index a8740eab..00000000 Binary files a/android/app/src/main/res/drawable-night/grant_perm.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png b/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png deleted file mode 100644 index 429c58b2..00000000 Binary files a/android/app/src/main/res/drawable-nodpi/example_appwidget_preview.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-nodpi/grant_perm.png b/android/app/src/main/res/drawable-nodpi/grant_perm.png deleted file mode 100644 index f6732a37..00000000 Binary files a/android/app/src/main/res/drawable-nodpi/grant_perm.png and /dev/null differ diff --git a/android/app/src/main/res/drawable-v21/app_widget_background.xml b/android/app/src/main/res/drawable-v21/app_widget_background.xml deleted file mode 100644 index 785445c6..00000000 --- a/android/app/src/main/res/drawable-v21/app_widget_background.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- -Background for widgets to make the rounded corners based on the -appWidgetRadius attribute value ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - - <corners android:radius="?attr/appWidgetRadius" /> - <solid android:color="?android:attr/colorBackground" /> -</shape> \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml b/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml deleted file mode 100644 index 007e2872..00000000 --- a/android/app/src/main/res/drawable-v21/app_widget_inner_view_background.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- -Background for views inside widgets to make the rounded corners based on the -appWidgetInnerRadius attribute value ---> -<shape xmlns:android="http://schemas.android.com/apk/res/android" - android:shape="rectangle"> - - <corners android:radius="?attr/appWidgetInnerRadius" /> - <solid android:color="?android:attr/colorAccent" /> -</shape> \ No newline at end of file diff --git a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index 2b068d11..00000000 --- a/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:aapt="http://schemas.android.com/aapt" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> - <aapt:attr name="android:fillColor"> - <gradient - android:endX="85.84757" - android:endY="92.4963" - android:startX="42.9492" - android:startY="49.59793" - android:type="linear"> - <item - android:color="#44000000" - android:offset="0.0" /> - <item - android:color="#00000000" - android:offset="1.0" /> - </gradient> - </aapt:attr> - </path> - <path - android:fillColor="#FFFFFF" - android:fillType="nonZero" - android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" - android:strokeWidth="1" - android:strokeColor="#00000000" /> -</vector> \ No newline at end of file diff --git a/android/app/src/main/res/drawable/arrow_back.xml b/android/app/src/main/res/drawable/arrow_back.xml deleted file mode 100644 index af7a5657..00000000 --- a/android/app/src/main/res/drawable/arrow_back.xml +++ /dev/null @@ -1,9 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="960" - android:viewportHeight="960"> - <path - android:pathData="m313,520 l224,224 -57,56 -320,-320 320,-320 57,56 -224,224h487v80L313,520Z" - android:fillColor="#e8eaed"/> -</vector> diff --git a/android/app/src/main/res/drawable/better_cpp_discord_pfp.gif b/android/app/src/main/res/drawable/better_cpp_discord_pfp.gif deleted file mode 100644 index be987aef..00000000 Binary files a/android/app/src/main/res/drawable/better_cpp_discord_pfp.gif and /dev/null differ diff --git a/android/app/src/main/res/drawable/burnt_github_pfp.png b/android/app/src/main/res/drawable/burnt_github_pfp.png deleted file mode 100644 index 0d5db231..00000000 Binary files a/android/app/src/main/res/drawable/burnt_github_pfp.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/colorpickerview_ss.png b/android/app/src/main/res/drawable/colorpickerview_ss.png deleted file mode 100644 index fea7dc35..00000000 Binary files a/android/app/src/main/res/drawable/colorpickerview_ss.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/discord_mark_blue.png b/android/app/src/main/res/drawable/discord_mark_blue.png deleted file mode 100644 index e9dc50d7..00000000 Binary files a/android/app/src/main/res/drawable/discord_mark_blue.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/discord_mark_white.png b/android/app/src/main/res/drawable/discord_mark_white.png deleted file mode 100644 index baececa9..00000000 Binary files a/android/app/src/main/res/drawable/discord_mark_white.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/fmtlib_logo.png b/android/app/src/main/res/drawable/fmtlib_logo.png deleted file mode 100644 index 19c66274..00000000 Binary files a/android/app/src/main/res/drawable/fmtlib_logo.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/github_mark.png b/android/app/src/main/res/drawable/github_mark.png deleted file mode 100644 index 10b895fc..00000000 Binary files a/android/app/src/main/res/drawable/github_mark.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/github_pfp.png b/android/app/src/main/res/drawable/github_pfp.png deleted file mode 100644 index bca86976..00000000 Binary files a/android/app/src/main/res/drawable/github_pfp.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/github_white_mark.png b/android/app/src/main/res/drawable/github_white_mark.png deleted file mode 100644 index d66bdeb4..00000000 Binary files a/android/app/src/main/res/drawable/github_white_mark.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/ic_launcher_background.xml b/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 07d5da9c..00000000 --- a/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="108dp" - android:height="108dp" - android:viewportWidth="108" - android:viewportHeight="108"> - <path - android:fillColor="#3DDC84" - android:pathData="M0,0h108v108h-108z" /> - <path - android:fillColor="#00000000" - android:pathData="M9,0L9,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,0L19,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M29,0L29,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M39,0L39,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M49,0L49,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M59,0L59,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M69,0L69,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M79,0L79,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M89,0L89,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M99,0L99,108" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,9L108,9" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,19L108,19" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,29L108,29" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,39L108,39" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,49L108,49" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,59L108,59" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,69L108,69" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,79L108,79" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,89L108,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M0,99L108,99" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,29L89,29" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,39L89,39" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,49L89,49" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,59L89,59" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,69L89,69" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M19,79L89,79" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M29,19L29,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M39,19L39,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M49,19L49,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M59,19L59,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M69,19L69,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> - <path - android:fillColor="#00000000" - android:pathData="M79,19L79,89" - android:strokeWidth="0.8" - android:strokeColor="#33FFFFFF" /> -</vector> diff --git a/android/app/src/main/res/drawable/icon_alert_yellow.xml b/android/app/src/main/res/drawable/icon_alert_yellow.xml deleted file mode 100644 index 2a895298..00000000 --- a/android/app/src/main/res/drawable/icon_alert_yellow.xml +++ /dev/null @@ -1,6 +0,0 @@ -<!-- By OOjs UI Team and other contributors, MIT, https://commons.wikimedia.org/w/index.php?curid=78132716 --> -<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:viewportHeight="20" android:viewportWidth="20" android:width="24dp"> - - <path android:fillColor="#FFffcc33" android:pathData="M19.64,16.36L11.53,2.3A1.85,1.85 0,0 0,10 1.21,1.85 1.85,0 0,0 8.48,2.3L0.36,16.36C-0.48,17.81 0.21,19 1.88,19h16.24c1.67,0 2.36,-1.19 1.52,-2.64zM11,16L9,16v-2h2zM11,12L9,12L9,6h2z"/> - -</vector> diff --git a/android/app/src/main/res/drawable/mini_arrow.xml b/android/app/src/main/res/drawable/mini_arrow.xml deleted file mode 100644 index 9863ff3e..00000000 --- a/android/app/src/main/res/drawable/mini_arrow.xml +++ /dev/null @@ -1,9 +0,0 @@ -<vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" - android:viewportWidth="24" - android:viewportHeight="24"> - <path - android:pathData="M12,16.5L6,10.5H18L12,16.5Z" - android:fillColor="@color/subText"/> -</vector> diff --git a/android/app/src/main/res/drawable/quartzitechunk_discord_pfp.png b/android/app/src/main/res/drawable/quartzitechunk_discord_pfp.png deleted file mode 100644 index 714a156c..00000000 Binary files a/android/app/src/main/res/drawable/quartzitechunk_discord_pfp.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/reddit_logo_16.png b/android/app/src/main/res/drawable/reddit_logo_16.png deleted file mode 100644 index ce7f853a..00000000 Binary files a/android/app/src/main/res/drawable/reddit_logo_16.png and /dev/null differ diff --git a/android/app/src/main/res/drawable/round_border.xml b/android/app/src/main/res/drawable/round_border.xml deleted file mode 100644 index 8f0802aa..00000000 --- a/android/app/src/main/res/drawable/round_border.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@color/buttonBg" /> <!-- Background color --> - <corners android:radius="16dp" /> <!-- Rounded corners radius --> -</shape> \ No newline at end of file diff --git a/android/app/src/main/res/drawable/tomlplusplus_banner.png b/android/app/src/main/res/drawable/tomlplusplus_banner.png deleted file mode 100644 index c9ee9963..00000000 Binary files a/android/app/src/main/res/drawable/tomlplusplus_banner.png and /dev/null differ diff --git a/android/app/src/main/res/layout/about_me_fragment.xml b/android/app/src/main/res/layout/about_me_fragment.xml deleted file mode 100644 index f3abcfa1..00000000 --- a/android/app/src/main/res/layout/about_me_fragment.xml +++ /dev/null @@ -1,362 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background" - tools:ignore="HardcodedText"> -<LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" > - - <androidx.appcompat.widget.Toolbar - android:id="@+id/toolbar_about_me" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:animateLayoutChanges="true" - android:background="@color/toolBar" - android:popupTheme="?attr/actionBarPopupTheme" - app:title="About Me" - app:navigationIcon="@drawable/arrow_back" - app:titleTextColor="@color/white" /> - - <!-- Developers/Contributors --> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="40dp" - android:gravity="center" - android:text="Developers/Contributors" - android:textColor="@color/text" - android:textSize="25sp" /> - - <!-- Toni's github --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="100dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:layout_marginTop="30dp" - android:src="@drawable/github_pfp" - app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Chip" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="15dp"> - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/toni_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/toni500_github" - android:textColor="@color/text" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="20dp" - android:text="Creator and maintainer of customfetch.\n(The application and widget you are currently using)" - android:textColor="@color/subText" /> - - <!-- Burnt's github --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="100dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:layout_marginTop="50dp" - android:src="@drawable/burnt_github_pfp" - app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Chip" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="15dp"> - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/burnt_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/burntranch_github" - android:textColor="#ffffff" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="10dp" - android:text="BestFriend and collaborator of our projects.\nCreated the first prototype of the codebase/program and also helped a lot about anything in technical" - android:textColor="@color/subText" /> - - <!-- BCPP discord server --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="100dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:layout_marginTop="50dp" - android:src="@drawable/better_cpp_discord_pfp" - app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Chip" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="15dp"> - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/discord_mark_blue" /> - - <TextView - android:id="@+id/bcpp_discord_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/better_cpp_discord_server" - android:textColor="@color/subText" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="20dp" - android:text="They helped me improving the codebase and also helped me with any issues I got. Also they were really patient lol" - android:textColor="@color/subText" /> - - <!-- Quartzitechunk github and discord --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="100dp" - android:layout_height="100dp" - android:layout_gravity="center" - android:layout_marginTop="50dp" - android:src="@drawable/quartzitechunk_discord_pfp" - app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Material3.Chip" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="15dp"> - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/quartzitechunk_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/quartzitechunk_github" - android:textColor="#ffffff" /> - </LinearLayout> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="10dp" > - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/discord_mark_blue" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="lonewolf_3333" - android:textColor="@color/text" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="20dp" - android:text="Tested customfetch for android and widget and reported bugs that could've been CyberPunk 2077 worthy of note before v1.0.0" - android:textColor="@color/subText" /> - - <!-- Libraries --> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="80dp" - android:gravity="center" - android:text="Libraries" - android:textColor="@color/text" - android:textSize="33sp" /> - - <!-- {fmt} --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="100dp" - android:layout_height="70dp" - android:layout_gravity="center" - android:src="@drawable/fmtlib_logo" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" > - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/fmtlib_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/fmtlib_github" - android:textColor="#ffffff" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="18dp" - android:text="Formatting library that provides a fast and safe alternative to C stdio and C++ iostreams." - android:textColor="@color/subText" /> - - <!-- toml++ --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="wrap_content" - android:layout_height="80dp" - android:layout_gravity="center" - android:layout_marginTop="35dp" - android:src="@drawable/tomlplusplus_banner" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="10dp" > - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/toml_lib_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/tomlplusplus_github" - android:textColor="#ffffff" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="10dp" - android:text="C++17 library for serializing and parsing TOML config files." - android:textColor="@color/subText" /> - - <!-- colorpickerview --> - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="wrap_content" - android:layout_height="80dp" - android:layout_gravity="center" - android:layout_marginTop="35dp" - android:src="@drawable/colorpickerview_ss" /> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="1dp" > - - <com.google.android.material.imageview.ShapeableImageView - android:layout_width="20dp" - android:layout_height="20dp" - android:src="@drawable/github_mark" /> - - <TextView - android:id="@+id/colorpickerview_github_link" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginStart="4dp" - android:text="@string/colorpickerview_github" - android:textColor="#ffffff" /> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="5dp" - android:gravity="center" - android:paddingHorizontal="20dp" - android:text="Android colorpicker for getting colors from any images by tapping on the desired color." - android:textColor="@color/subText" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:layout_marginTop="85dp" - android:layout_marginBottom="50dp" - android:gravity="center" - android:paddingHorizontal="10dp" - android:text="Did you know that customfetch is the only neofetch like program that is both: terminal app, GUI app and android widget?\nAt least by now :)" - android:textColor="@color/text" /> - -</LinearLayout> -</androidx.core.widget.NestedScrollView> diff --git a/android/app/src/main/res/layout/activity_main.xml b/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index 26850821..00000000 --- a/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,329 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<androidx.core.widget.NestedScrollView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background" - android:id="@+id/activity_main" - style="@style/Theme.MainActivity" - tools:context=".MainActivity" - tools:ignore="MissingConstraints,HardcodedText" > -<androidx.constraintlayout.widget.ConstraintLayout - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <Space - android:id="@+id/spacer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="15sp" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - - <LinearLayout - android:id="@+id/testConfigFile" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:orientation="vertical" - android:clickable="true" - android:focusable="true" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="20dp" - app:layout_constraintTop_toBottomOf="@id/spacer" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Test config file" - android:textColor="@color/text" - android:textSize="16sp" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Select a config file to test" - android:textColor="@color/subText" - android:textSize="14sp" - android:layout_marginTop="8dp" /> - </LinearLayout> - - <LinearLayout - android:id="@+id/widgetSettings" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:orientation="vertical" - android:clickable="true" - android:focusable="true" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="20dp" - app:layout_constraintTop_toBottomOf="@id/testConfigFile" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" > - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Widget Settings" - android:textColor="@color/text" - android:textSize="16sp" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="General settings for all widgets" - android:textColor="@color/subText" - android:textSize="14sp" - android:layout_marginTop="8dp" /> - - <TextView - android:id="@+id/click_me_settings" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginStart="210dp" - android:visibility="gone" - android:textSize="16sp" - android:textColor="@color/text" - android:text="CLICK ME NOW" /> - - </LinearLayout> - - <LinearLayout - android:id="@+id/aboutMe" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="20dp" - app:layout_constraintTop_toBottomOf="@id/widgetSettings" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="About me" - android:textColor="@color/text" - android:textSize="16sp" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="About me and customfetch" - android:textColor="@color/subText" - android:textSize="14sp" - android:layout_marginTop="8dp" /> - </LinearLayout> - - <!-- Title with Arrow --> - <LinearLayout - android:id="@+id/collapseSocialsTitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:orientation="horizontal" - android:layout_marginTop="15dp" - app:layout_constraintTop_toBottomOf="@id/aboutMe" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"> - - <ImageView - android:id="@+id/arrowIcon" - android:layout_width="24dp" - android:layout_height="24dp" - android:src="@drawable/mini_arrow" /> - - <TextView - android:id="@+id/titleText" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:text="Social Links" - android:gravity="center" - android:textSize="16sp" - android:textStyle="bold" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - - </LinearLayout> - - <net.cachapa.expandablelayout.ExpandableLayout - android:id="@+id/collapseSocialsLayout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:el_duration="450" - app:el_expanded="false" - app:el_parallax="1" - app:layout_constraintTop_toBottomOf="@id/collapseSocialsTitle" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" > - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" > - - <!-- Discord server --> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/join_discord_link" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="10dp" - app:layout_constraintTop_toBottomOf="@id/collapseSocialsLayout" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"> - - <TextView - android:id="@+id/_text_discord" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/join_discord_server" - android:textColor="@color/white" - android:textSize="16sp" - app:layout_constraintStart_toStartOf="parent" /> - - <TextView - android:id="@+id/_sub_text_discord" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Chat with us on how\nwe can improve customfetch" - android:textColor="#CCCCCC" - android:textSize="14sp" - android:layout_marginTop="8dp" - app:layout_constraintTop_toBottomOf="@id/_text_discord" /> - - <ImageView - android:layout_width="70dp" - android:layout_height="70dp" - android:src="@drawable/discord_mark_white" - android:layout_gravity="end" - app:layout_constraintEnd_toEndOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <!-- subReddit --> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/join_customfetch_reddit" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="10dp" - app:layout_constraintTop_toBottomOf="@id/join_discord_link" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" > - - <TextView - android:id="@+id/_text_reddit" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/join_subreddit" - android:textColor="@color/white" - android:textSize="16sp" - app:layout_constraintStart_toStartOf="parent" /> - - <TextView - android:id="@+id/_sub_text_reddit" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Share your screenshots\nand feedback about customfetch" - android:textColor="#CCCCCC" - android:textSize="14sp" - android:layout_marginTop="8dp" - app:layout_constraintTop_toBottomOf="@id/_text_reddit" /> - - <ImageView - android:layout_width="70dp" - android:layout_height="70dp" - android:src="@drawable/reddit_logo_16" - android:layout_gravity="end" - app:layout_constraintEnd_toEndOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - - <!-- GitHub repo --> - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/star_github_repo" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:background="@drawable/round_border" - android:padding="24dp" - android:layout_marginTop="24dp" - android:layout_margin="10dp" - app:layout_constraintTop_toBottomOf="@id/join_customfetch_reddit" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent"> - - <TextView - android:id="@+id/_text_github" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Star customfetch" - android:textColor="@color/white" - android:textSize="16sp" - app:layout_constraintStart_toStartOf="parent" /> - - <TextView - android:id="@+id/_sub_text_github" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Leave a star to our GitHub repo.\nIt means a lot to us <3" - android:textColor="#CCCCCC" - android:textSize="14sp" - android:layout_marginTop="8dp" - app:layout_constraintTop_toBottomOf="@id/_text_github" /> - - <ImageView - android:layout_width="70dp" - android:layout_height="70dp" - android:src="@drawable/github_white_mark" - android:layout_gravity="end" - app:layout_constraintEnd_toEndOf="parent" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - </LinearLayout> - </net.cachapa.expandablelayout.ExpandableLayout> - - <TextView - android:id="@+id/app_version" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - android:layout_marginTop="470dp" - app:layout_constraintTop_toBottomOf="@id/aboutMe" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" - android:textColor="@color/text" - android:text="yes" /> - - <Space - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - android:layout_marginTop="45dp" - app:layout_constraintTop_toBottomOf="@id/app_version" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout> -</androidx.core.widget.NestedScrollView> - diff --git a/android/app/src/main/res/layout/colorpickerview_layout.xml b/android/app/src/main/res/layout/colorpickerview_layout.xml deleted file mode 100644 index 2027853f..00000000 --- a/android/app/src/main/res/layout/colorpickerview_layout.xml +++ /dev/null @@ -1,55 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" > - - <EditText - android:id="@+id/color_picker_hex" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:hint="Hex Color Code (#AARRGGBB)" - android:inputType="text" /> - - <com.skydoves.colorpickerview.ColorPickerView - android:id="@+id/colorPickerView" - android:layout_width="match_parent" - android:layout_height="250dp" - app:initialColor="@color/cardview_dark_background" /> - - <!-- padding separator --> - <View - android:layout_width="wrap_content" - android:layout_height="20dp" /> - - <com.skydoves.colorpickerview.sliders.AlphaSlideBar - android:id="@+id/alphaSlideBar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:selector_AlphaSlideBar="@drawable/colorpickerview_wheel" - app:borderColor_AlphaSlideBar="@android:color/darker_gray" - app:borderSize_AlphaSlideBar="5"/> - - <!-- padding separator --> - <View - android:layout_width="wrap_content" - android:layout_height="20dp" /> - - <com.skydoves.colorpickerview.sliders.BrightnessSlideBar - android:id="@+id/brightnessSlideBar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - app:borderColor_BrightnessSlider="@android:color/darker_gray" - app:borderSize_BrightnessSlider="5" - app:selector_BrightnessSlider="@drawable/colorpickerview_wheel" /> - - <View - android:id="@+id/colorPreview" - android:layout_width="match_parent" - android:layout_height="50dp" - android:layout_marginTop="16dp" - android:background="#FFFFFF" /> - -</LinearLayout> \ No newline at end of file diff --git a/android/app/src/main/res/layout/customfetch.xml b/android/app/src/main/res/layout/customfetch.xml deleted file mode 100644 index f350c3a0..00000000 --- a/android/app/src/main/res/layout/customfetch.xml +++ /dev/null @@ -1,21 +0,0 @@ -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - style="@style/Widget.Customfetch_android.AppWidget.Container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:id="@+id/widget_root" - android:theme="@style/Theme.MainActivity.AppWidgetContainer"> - - <TextView - android:id="@+id/customfetch_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_margin="8dp" - android:focusable="true" - android:contentDescription="@string/appwidget_text" - android:text="@string/appwidget_text" - android:textSize="7sp" - android:fontFamily="monospace" - tools:ignore="SmallSp" /> - -</LinearLayout> diff --git a/android/app/src/main/res/layout/customfetch_configure.xml b/android/app/src/main/res/layout/customfetch_configure.xml deleted file mode 100644 index df64ee12..00000000 --- a/android/app/src/main/res/layout/customfetch_configure.xml +++ /dev/null @@ -1,196 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical" - android:padding="16dp" - tools:ignore="HardcodedText"> -<LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:labelFor="@id/arguments_configure" - android:text="@string/configure" /> - - <EditText - android:id="@+id/arguments_configure" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:autofillHints="EXAMPLE" - android:inputType="text" /> - - <CheckBox - android:id="@+id/truncate_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:labelFor="@+id/arguments_configure" - android:text="@string/disable_wrap_lines" /> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:labelFor="@id/arguments_configure" - android:text="@string/additional_truncate" /> - - <EditText - android:id="@+id/additional_truncate_width" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="0.6" - android:inputType="number" /> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingTop="30dp" - android:labelFor="@id/select_text_color" - android:text="Widget default text color" /> - - <RadioGroup - android:id="@+id/select_text_color" - android:layout_width="wrap_content" - android:layout_height="wrap_content" > - - <RadioButton - android:id="@+id/radio_default_text_color" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Default color (#FF8F9099)" - android:checked="true" /> - - <RadioButton - android:id="@+id/radio_custom_text_color" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/custom_color" /> - - </RadioGroup> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/custom_text_color_select" - android:orientation="vertical" - android:visibility="gone" > - - <!-- Here we'll add from customfetchConfigureActivity.kt the ColorPickerView layout --> - - </LinearLayout> - - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingTop="30dp" - android:labelFor="@id/select_bg_color" - android:text="@string/background_color" /> - - <RadioGroup - android:id="@+id/select_bg_color" - android:layout_width="wrap_content" - android:layout_height="wrap_content" > - - <RadioButton - android:id="@+id/radio_system_bg_color" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/use_system_theme_color" /> - - <RadioButton - android:id="@+id/radio_transparent_bg" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/transparent_background" /> - - <RadioButton - android:id="@+id/radio_custom_colors" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/custom_color"/> - - </RadioGroup> - - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:id="@+id/custom_bg_color_select" - android:orientation="vertical" - android:visibility="gone" > - - <!-- Here we'll add from customfetchConfigureActivity.kt the ColorPickerView layout --> - - </LinearLayout> - - <Button - android:id="@+id/add_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:text="@string/add_widget" /> - - <HorizontalScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="40dp" > - <LinearLayout - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal"> - - <Button - android:id="@+id/btn_args_help" - style="@style/Widget.AppCompat.Button.Small" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Help command line options" /> - - <Button - android:id="@+id/btn_config_help" - style="@style/Widget.AppCompat.Button.Small" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Help configuration" - android:layout_marginStart="10dp" /> - - <Button - android:id="@+id/btn_modules_help" - style="@style/Widget.AppCompat.Button.Small" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Help info tag modules list" - android:layout_marginStart="10dp" /> - - <Button - android:id="@+id/btn_logos_list" - style="@style/Widget.AppCompat.Button.Small" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="List of ascii logos" - android:layout_marginStart="10dp" /> - - </LinearLayout> - </HorizontalScrollView> - - <HorizontalScrollView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="10dp"> - - <TextView - android:id="@+id/docs_help" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginBottom="8dp" - android:fontFamily="monospace" - android:text="yooooloooooooooooooooooooooooooooooooooooooooooo" /> - - </HorizontalScrollView> - -</LinearLayout> -</androidx.core.widget.NestedScrollView> diff --git a/android/app/src/main/res/layout/grant_perm.xml b/android/app/src/main/res/layout/grant_perm.xml deleted file mode 100644 index 57256987..00000000 --- a/android/app/src/main/res/layout/grant_perm.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:gravity="center"> - - <ImageView - android:id="@+id/dialog_imageview" - android:layout_width="254dp" - android:layout_height="185dp" - android:layout_gravity="center" - android:adjustViewBounds="true" - android:scaleType="fitCenter" - android:src="@drawable/grant_perm" /> - -</LinearLayout> \ No newline at end of file diff --git a/android/app/src/main/res/layout/settings_layout.xml b/android/app/src/main/res/layout/settings_layout.xml deleted file mode 100644 index e401110b..00000000 --- a/android/app/src/main/res/layout/settings_layout.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background" - android:orientation="vertical"> - - <androidx.appcompat.widget.Toolbar - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/toolBar" - android:id="@+id/toolbar_settings" - app:title="Widget Settings" - app:navigationIcon="@drawable/arrow_back" - android:theme="@style/ThemeOverlay.AppCompat.ActionBar" - app:popupTheme="@style/ThemeOverlay.AppCompat.Light" - app:titleTextColor="@color/white" /> - - <FrameLayout - android:id="@android:id/list_container" - android:layout_width="match_parent" - android:layout_height="match_parent" /> - -</LinearLayout> \ No newline at end of file diff --git a/android/app/src/main/res/layout/test_config_fragment.xml b/android/app/src/main/res/layout/test_config_fragment.xml deleted file mode 100644 index fcc42a9b..00000000 --- a/android/app/src/main/res/layout/test_config_fragment.xml +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="@color/background" > - - <androidx.appcompat.widget.Toolbar - android:id="@+id/toolbar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@color/toolBar" - android:popupTheme="?attr/actionBarPopupTheme" - android:animateLayoutChanges="true" - app:titleTextColor="#ffFFff" - app:title="Test config file" /> - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - android:gravity="center" > - - <TextView - android:id="@+id/titleResult" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="20dp"/> - - <TextView - android:id="@+id/testConfigResult" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center" - android:gravity="center" - android:layout_marginTop="10dp" - android:textColor="@color/text" - android:fontFamily="monospace" /> - - </LinearLayout> - -</LinearLayout> \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index eca70cfe..00000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@drawable/ic_launcher_background" /> - <foreground android:drawable="@drawable/ic_launcher_foreground" /> -</adaptive-icon> \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index eca70cfe..00000000 --- a/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> - <background android:drawable="@drawable/ic_launcher_background" /> - <foreground android:drawable="@drawable/ic_launcher_foreground" /> -</adaptive-icon> \ No newline at end of file diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78e..00000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d1..00000000 Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64..00000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611da..00000000 Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a3070..00000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a6956..00000000 Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77f..00000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f508..00000000 Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d6427..00000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae37..00000000 Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/android/app/src/main/res/values-night-v31/themes.xml b/android/app/src/main/res/values-night-v31/themes.xml deleted file mode 100644 index eebc67ac..00000000 --- a/android/app/src/main/res/values-night-v31/themes.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <!-- - Having themes.xml for night-v31 because of the priority order of the resource qualifiers. - --> - <style name="Theme.MainActivity.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight"> - <item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item> - <item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item> - </style> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values-night/colors.xml b/android/app/src/main/res/values-night/colors.xml deleted file mode 100644 index df3afd9e..00000000 --- a/android/app/src/main/res/values-night/colors.xml +++ /dev/null @@ -1,9 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="background">#FF2A2A2A</color> - <color name="buttonBg">#3D3D3D</color> - <color name="reverseButtonBg">#5A5A5A</color> - <color name="toolBar">#4F4F4F</color> - <color name="text">#FFFFFF</color> - <color name="subText">#BBBBBB</color> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values-night/themes.xml b/android/app/src/main/res/values-night/themes.xml deleted file mode 100644 index 8cd023cf..00000000 --- a/android/app/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,17 +0,0 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> - <!-- Base application theme. --> - <style name="Theme.MainActivity" parent="Theme.MaterialComponents.DayNight.NoActionBar"> - <!-- Primary brand color. --> - <item name="colorPrimary">@color/purple_200</item> - <item name="colorPrimaryVariant">@color/purple_500</item> - <item name="colorOnPrimary">@color/black</item> - <!-- Secondary brand color. --> - <item name="colorSecondary">@color/teal_200</item> - <item name="colorSecondaryVariant">@color/teal_200</item> - <item name="colorOnSecondary">@color/black</item> - <!-- Status bar color. --> - <item name="android:statusBarColor">@color/background</item> - <!-- Customize your theme here. --> - <item name="preferenceTheme">@style/PrefsTheme</item> - </style> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values-v21/styles.xml b/android/app/src/main/res/values-v21/styles.xml deleted file mode 100644 index 13527e2b..00000000 --- a/android/app/src/main/res/values-v21/styles.xml +++ /dev/null @@ -1,14 +0,0 @@ -<resources> - - <style name="Widget.Customfetch_android.AppWidget.Container" parent="android:Widget"> - <item name="android:id">@android:id/background</item> - <item name="android:padding">?attr/appWidgetPadding</item> - <item name="android:background">@drawable/app_widget_background</item> - </style> - - <style name="Widget.Customfetch_android.AppWidget.InnerView" parent="android:Widget"> - <item name="android:padding">?attr/appWidgetPadding</item> - <item name="android:background">@drawable/app_widget_inner_view_background</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values-v31/colors.xml b/android/app/src/main/res/values-v31/colors.xml deleted file mode 100644 index eed51668..00000000 --- a/android/app/src/main/res/values-v31/colors.xml +++ /dev/null @@ -1,5 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="main_500">@android:color/system_accent1_500</color> - <color name="main_dark_800">@android:color/system_accent1_800</color> -</resources> diff --git a/android/app/src/main/res/values-v31/styles.xml b/android/app/src/main/res/values-v31/styles.xml deleted file mode 100644 index 8d4e14be..00000000 --- a/android/app/src/main/res/values-v31/styles.xml +++ /dev/null @@ -1,16 +0,0 @@ -<resources> - - <style name="Widget.Customfetch_android.AppWidget.Container" parent="android:Widget"> - <item name="android:id">@android:id/background</item> - <item name="android:padding">?attr/appWidgetPadding</item> - <item name="android:background">@drawable/app_widget_background</item> - <item name="android:clipToOutline">true</item> - </style> - - <style name="Widget.Customfetch_android.AppWidget.InnerView" parent="android:Widget"> - <item name="android:padding">?attr/appWidgetPadding</item> - <item name="android:background">@drawable/app_widget_inner_view_background</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - <item name="android:clipToOutline">true</item> - </style> -</resources> diff --git a/android/app/src/main/res/values-v31/themes.xml b/android/app/src/main/res/values-v31/themes.xml deleted file mode 100644 index 8e2b47d2..00000000 --- a/android/app/src/main/res/values-v31/themes.xml +++ /dev/null @@ -1,11 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <!-- - Having themes.xml for v31 variant because @android:dimen/system_app_widget_background_radius - and @android:dimen/system_app_widget_internal_padding requires API level 31 - --> - <style name="Theme.MainActivity.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault.DayNight"> - <item name="appWidgetRadius">@android:dimen/system_app_widget_background_radius</item> - <item name="appWidgetInnerRadius">@android:dimen/system_app_widget_inner_radius</item> - </style> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/arrays.xml b/android/app/src/main/res/values/arrays.xml deleted file mode 100644 index cef61a01..00000000 --- a/android/app/src/main/res/values/arrays.xml +++ /dev/null @@ -1,14 +0,0 @@ -<resources> - <!-- Reply Preference --> - <string-array name="default_bg_colors"> - <item>System theme color</item> - <item>Transparent color</item> - <item>Custom color</item> - </string-array> - - <string-array name="default_bg_colors_values"> - <item>system_bg_color</item> - <item>transparent_bg</item> - <item>custom_bg_color</item> - </string-array> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml deleted file mode 100644 index 7781ac86..00000000 --- a/android/app/src/main/res/values/attrs.xml +++ /dev/null @@ -1,7 +0,0 @@ -<resources> - <declare-styleable name="AppWidgetAttrs"> - <attr name="appWidgetPadding" format="dimension" /> - <attr name="appWidgetInnerRadius" format="dimension" /> - <attr name="appWidgetRadius" format="dimension" /> - </declare-styleable> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml deleted file mode 100644 index af1fd2af..00000000 --- a/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,26 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="background">#E2E2E2</color> - <color name="buttonBg">#B8B8B8</color> - <color name="reverseButtonBg">#727272</color> - <color name="toolBar">#727272</color> - <color name="text">#121212</color> - <color name="textAlt">#121212</color> - <color name="subText">#505050</color> - - <color name="purple_200">#FFBB86FC</color> - <color name="purple_500">#FF6200EE</color> - <color name="purple_700">#FF3700B3</color> - <color name="teal_200">#FF03DAC5</color> - <color name="teal_700">#FF018786</color> - <color name="black">#FF000000</color> - <color name="white">#FFFFFFFF</color> - <color name="light_blue_50">#FFE1F5FE</color> - <color name="light_blue_200">#FF81D4FA</color> - <color name="light_blue_600">#FF039BE5</color> - <color name="light_blue_900">#FF01579B</color> - - <array name="default_colors_widget_text"> - <item>#FF8F9099</item> <!-- default text view color for the widget, found by debugging some things --> - </array> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml deleted file mode 100644 index 4db8c590..00000000 --- a/android/app/src/main/res/values/dimens.xml +++ /dev/null @@ -1,10 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - - <!-- -Refer to App Widget Documentation for margin information -http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout - --> - <dimen name="widget_margin">0dp</dimen> - -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml deleted file mode 100644 index c19291e4..00000000 --- a/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,23 +0,0 @@ -<resources> - <string name="app_name">Customfetch Widget</string> - <string name="appwidget_text">EXAMPLE</string> - <string name="configure">Configure\n(insert command line options)</string> - <string name="additional_truncate">Additional truncate text width\n(insert number bigger than <b>0.20</b>, or smaller to ignore)</string> - <string name="add_widget">Add widget</string> - <string name="app_widget_description">Neofetch like program, which its focus point is the performance and customizability</string> - <string name="modules_list">Display modules list</string> - <string name="disable_wrap_lines">Truncate text (may be unstable, use it only on phones)</string> - <string name="background_color">Background color</string> - <string name="use_system_theme_color">System theme color</string> - <string name="transparent_background">Transparent background</string> - <string name="custom_color">Custom color</string> - <string name="join_discord_server">Join our discord server</string> - <string name="better_cpp_discord_server"><a href="https://discord.gg/uSzTjkXtAM/">Better C++ discord server</a></string> - <string name="burntranch_github"><a href="https://github.com/BurntRanch/">BurntRanch</a></string> - <string name="quartzitechunk_github"><a href="https://github.com/Quartzitechunk/">Quartzitechunk</a></string> - <string name="toni500_github"><a href="https://github.com/Toni500github/">Toni500github</a></string> - <string name="fmtlib_github"><a href="https://github.com/fmtlib/fmt">fmtlib</a></string> - <string name="tomlplusplus_github"><a href="https://github.com/marzer/tomlplusplus">tomlplusplus</a></string> - <string name="colorpickerview_github"><a href="https://github.com/skydoves/ColorPickerView">ColorPickerView</a></string> - <string name="join_subreddit">Join r/customfetch</string> -</resources> diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml deleted file mode 100644 index 3a7dd7eb..00000000 --- a/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,22 +0,0 @@ -<resources> - - <style name="Widget.Customfetch_android.AppWidget.Container" parent="android:Widget"> - <item name="android:id">@android:id/background</item> - <item name="android:background">?android:attr/colorBackground</item> - </style> - - <style name="Widget.Customfetch_android.AppWidget.InnerView" parent="android:Widget"> - <item name="android:background">?android:attr/colorBackground</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> - </style> - - <style name="PrefsTheme" parent="PreferenceThemeOverlay.v14.Material"> - <item name="android:layout">@layout/settings_layout</item> - </style> - - <style name="ShapeAppearanceOverlay.Material3.Chip" parent="Theme.MaterialComponents"> - <item name="cornerFamily">rounded</item> - <item name="cornerSize">50%</item> - </style> - -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml deleted file mode 100644 index 6e3aec4a..00000000 --- a/android/app/src/main/res/values/themes.xml +++ /dev/null @@ -1,32 +0,0 @@ -<resources xmlns:tools="http://schemas.android.com/tools"> - <!-- Base application theme. --> - <style name="Theme.MainActivity" parent="Theme.AppCompat.DayNight.NoActionBar"> - <!-- Primary brand color. --> - <item name="colorPrimary">@color/light_blue_200</item> - <item name="colorPrimaryVariant">@color/light_blue_600</item> - <item name="colorOnPrimary">@color/white</item> - <!-- Secondary brand color. --> - <item name="colorSecondary">@color/teal_200</item> - <item name="colorSecondaryVariant">@color/teal_700</item> - <item name="colorOnSecondary">@color/black</item> - <!-- Status bar color. --> - <item name="android:statusBarColor">@color/background</item> - <!-- Customize your theme here. --> - <item name="preferenceTheme">@style/PrefsTheme</item> - </style> - - <style name="Theme.MainActivity.AppWidgetContainerParent" parent="@android:style/Theme.DeviceDefault"> - <!-- Radius of the outer bound of widgets to make the rounded corners --> - <item name="appWidgetRadius">16dp</item> - <!-- - Radius of the inner view's bound of widgets to make the rounded corners. - It needs to be 8dp or less than the value of appWidgetRadius - --> - <item name="appWidgetInnerRadius">8dp</item> - </style> - - <style name="Theme.MainActivity.AppWidgetContainer" parent="Theme.MainActivity.AppWidgetContainerParent"> - <!-- Apply padding to avoid the content of the widget colliding with the rounded corners --> - <item name="appWidgetPadding">16dp</item> - </style> -</resources> \ No newline at end of file diff --git a/android/app/src/main/res/xml/backup_rules.xml b/android/app/src/main/res/xml/backup_rules.xml deleted file mode 100644 index fa0f996d..00000000 --- a/android/app/src/main/res/xml/backup_rules.xml +++ /dev/null @@ -1,13 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - Sample backup rules file; uncomment and customize as necessary. - See https://developer.android.com/guide/topics/data/autobackup - for details. - Note: This file is ignored for devices older that API 31 - See https://developer.android.com/about/versions/12/backup-restore ---> -<full-backup-content> - <!-- - <include domain="sharedpref" path="."/> - <exclude domain="sharedpref" path="device.xml"/> ---> -</full-backup-content> \ No newline at end of file diff --git a/android/app/src/main/res/xml/customfetch_info.xml b/android/app/src/main/res/xml/customfetch_info.xml deleted file mode 100644 index ef0022cd..00000000 --- a/android/app/src/main/res/xml/customfetch_info.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" - android:configure="org.toni.customfetch_android.widget.CustomfetchConfigureActivity" - android:description="@string/app_widget_description" - android:initialKeyguardLayout="@layout/customfetch" - android:initialLayout="@layout/customfetch" - android:minWidth="40dp" - android:minHeight="40dp" - android:previewImage="@drawable/example_appwidget_preview" - android:resizeMode="horizontal|vertical" - android:targetCellWidth="1" - android:targetCellHeight="1" - android:updatePeriodMillis="14400000" - android:widgetCategory="home_screen" /> diff --git a/android/app/src/main/res/xml/data_extraction_rules.xml b/android/app/src/main/res/xml/data_extraction_rules.xml deleted file mode 100644 index 9ee9997b..00000000 --- a/android/app/src/main/res/xml/data_extraction_rules.xml +++ /dev/null @@ -1,19 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - Sample data extraction rules file; uncomment and customize as necessary. - See https://developer.android.com/about/versions/12/backup-restore#xml-changes - for details. ---> -<data-extraction-rules> - <cloud-backup> - <!-- TODO: Use <include> and <exclude> to control what is backed up. - <include .../> - <exclude .../> - --> - </cloud-backup> - <!-- - <device-transfer> - <include .../> - <exclude .../> - </device-transfer> - --> -</data-extraction-rules> \ No newline at end of file diff --git a/android/app/src/main/res/xml/root_preferences.xml b/android/app/src/main/res/xml/root_preferences.xml deleted file mode 100644 index 8233647b..00000000 --- a/android/app/src/main/res/xml/root_preferences.xml +++ /dev/null @@ -1,50 +0,0 @@ -<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:android="http://schemas.android.com/apk/res/android"> - - <PreferenceCategory app:title="Default Configuration"> - - <EditTextPreference - app:key="default_args" - app:title="Default command line options" - app:defaultValue="-D /data/user/0/org.toni.customfetch_android/files -a small" - app:useSimpleSummaryProvider="true" /> - - <SwitchPreferenceCompat - app:key="always_truncate" - app:title="Always truncate text" /> - - <EditTextPreference - app:key="additional_truncate" - app:title="Default additional truncate width" - app:defaultValue="0.6" - android:inputType="number" - app:useSimpleSummaryProvider="true" /> - - <com.jaredrummler.android.colorpicker.ColorPreferenceCompat - app:key="default_widget_text_color" - app:title="Default widget text custom color" - android:defaultValue="0xFF8F9099" - app:cpv_colorPresets="@array/default_colors_widget_text" - app:cpv_previewSize="large" - app:cpv_showColorShades="false" /> - - <ListPreference - app:key="default_bg_color" - app:title="Default background color" - app:defaultValue="system_bg_color" - app:entries="@array/default_bg_colors" - app:entryValues="@array/default_bg_colors_values" - app:useSimpleSummaryProvider="true" /> - - <com.jaredrummler.android.colorpicker.ColorPreferenceCompat - app:key="default_bg_custom_color" - app:title="Default background custom color" - android:defaultValue="0x890099CC" - android:enabled="false" - app:cpv_previewSize="large" - app:cpv_showAlphaSlider="true" - app:cpv_showColorShades="false" /> - - </PreferenceCategory> - -</PreferenceScreen> \ No newline at end of file diff --git a/android/app/src/test/java/com/example/settings_testing/ExampleUnitTest.kt b/android/app/src/test/java/com/example/settings_testing/ExampleUnitTest.kt deleted file mode 100644 index ae0967a3..00000000 --- a/android/app/src/test/java/com/example/settings_testing/ExampleUnitTest.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.settings_testing - -import org.junit.Test - -import org.junit.Assert.* - -/** - * Example local unit test, which will execute on the development machine (host). - * - * See [testing documentation](http://d.android.com/tools/testing). - */ -class ExampleUnitTest { - @Test - fun addition_isCorrect() { - assertEquals(4, 2 + 2) - } -} \ No newline at end of file diff --git a/android/build.gradle.kts b/android/build.gradle.kts deleted file mode 100644 index 922f5511..00000000 --- a/android/build.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - alias(libs.plugins.android.application) apply false - alias(libs.plugins.kotlin.android) apply false -} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties deleted file mode 100644 index 0ef260e9..00000000 --- a/android/gradle.properties +++ /dev/null @@ -1,24 +0,0 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. For more details, visit -# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects -# org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true -# Kotlin code style for this project: "official" or "obsolete": -kotlin.code.style=official -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true -newArchEnabled=false diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml deleted file mode 100644 index 49c870b1..00000000 --- a/android/gradle/libs.versions.toml +++ /dev/null @@ -1,39 +0,0 @@ -[versions] -agp = "8.7.2" -colorpickerpref = "1.1.0" -colorpickerview = "2.3.0" -expandablelayout = "2.9.2" -htmlspanner = "0.4" -kotlin = "1.9.24" -coreKtx = "1.15.0" -junit = "4.13.2" -junitVersion = "1.2.1" -espressoCore = "3.6.1" -appcompat = "1.7.0" -material = "1.12.0" -constraintlayout = "2.2.0" -navigationFragmentKtx = "2.8.5" -navigationUiKtx = "2.8.5" -preferenceKtx = "1.2.1" -preference = "1.2.1" - -[libraries] -androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -colorpickerpref = { module = "com.jaredrummler:colorpicker", version.ref = "colorpickerpref" } -colorpickerview = { module = "com.github.skydoves:colorpickerview", version.ref = "colorpickerview" } -expandablelayout = { module = "com.github.cachapa:ExpandableLayout", version.ref = "expandablelayout" } -htmlspanner = { module = "com.github.NightWhistler:HtmlSpanner", version.ref = "htmlspanner" } -junit = { group = "junit", name = "junit", version.ref = "junit" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } -androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } -androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } -material = { group = "com.google.android.material", name = "material", version.ref = "material" } -androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } -androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } -androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } -androidx-preference-ktx = { group = "androidx.preference", name = "preference-ktx", version.ref = "preferenceKtx" } -androidx-preference = { group = "androidx.preference", name = "preference", version.ref = "preference" } - -[plugins] -android-application = { id = "com.android.application", version.ref = "agp" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index e708b1c0..00000000 Binary files a/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 3a0c85ea..00000000 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Fri Nov 15 17:27:43 CET 2024 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/android/gradlew b/android/gradlew deleted file mode 100755 index 4f906e0c..00000000 --- a/android/gradlew +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env sh - -# -# Copyright 2015 the original author or authors. -# -# 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 -# -# https://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. -# - -############################################################################## -## -## Gradle start up script for UN*X -## -############################################################################## - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" - -warn () { - echo "$*" -} - -die () { - echo - echo "$*" - echo - exit 1 -} - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; -esac - -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" - else - JAVACMD="$JAVA_HOME/bin/java" - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." -fi - -# Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi -fi - -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi - -# For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi - # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" - fi - i=`expr $i + 1` - done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac -fi - -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat deleted file mode 100644 index ac1b06f9..00000000 --- a/android/gradlew.bat +++ /dev/null @@ -1,89 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts deleted file mode 100644 index 79b7af40..00000000 --- a/android/settings.gradle.kts +++ /dev/null @@ -1,26 +0,0 @@ -pluginManagement { - repositories { - google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - } - } - maven { url = uri("https://jitpack.io") } - mavenCentral() - gradlePluginPortal() - } -} -dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) - repositories { - google() - maven { url = uri("https://jitpack.io") } - maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots") } - mavenCentral() - } -} - -rootProject.name = "customfetch_android" -include(":app") diff --git a/assets/ascii/celos.txt b/assets/ascii/celos.txt index d060340f..e8eb627f 100644 --- a/assets/ascii/celos.txt +++ b/assets/ascii/celos.txt @@ -12,7 +12,7 @@ ${black}#################################### ${magenta}MMMMMMMMMMMMMc ${magenta} "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM: ${magenta} "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" ${magenta} 'MMMMMMMMM*******************************: -${magenta} \"MMMMMM ${black}##################################### +${magenta} \\"MMMMMM ${black}##################################### ${magenta} ${magenta}`:MMMMMMmmmmmmmmmmmmmmmmmmmmmmmmmmmmm; ${magenta} `"MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM" ${magenta} `":MMMMMMMMMMMMMMMMMMMMMMMMM;' diff --git a/assets/ascii/dragonfly.txt b/assets/ascii/dragonfly.txt index 0009cb7a..6f863e18 100644 --- a/assets/ascii/dragonfly.txt +++ b/assets/ascii/dragonfly.txt @@ -1,11 +1,11 @@ ${1},--, ${red}| ${1},--, ${1}| `-, ${red},^, ${1},-' | -${1} `, `-, ${yellow}(/ \) ${1},-' ,' -${1} `-, `-,${red}/ \${1},-' ,-' +${1} `, `-, ${yellow}(/ \\) ${1},-' ,' +${1} `-, `-,${red}/ \\${1},-' ,-' ${1} `------${red}( )${1}------' ${1} ,----------${red}( )${1}----------, ${1} | _,-${red}( )${1}-,_ | -${1} `-,__,-' ${red}\ /${1} `-,__,-' +${1} `-,__,-' ${red}\\ /${1} `-,__,-' ${red} | | ${red} | | ${red} | | diff --git a/assets/ascii/dragonfly_old.txt b/assets/ascii/dragonfly_old.txt index 97280531..31867d79 100644 --- a/assets/ascii/dragonfly_old.txt +++ b/assets/ascii/dragonfly_old.txt @@ -1,14 +1,14 @@ ${red} .-. ${yellow} ()${red}I${yellow}() ${red} "==.__:-:__.==" -${red} "==.__/~|~\__.==" +${red} "==.__/~|~\\__.==" ${red} "==._( Y )_.==" - ${white}.-'~~""~=--...,__${red}\/|\/${white}__,...--=~""~~'-. + ${white}.-'~~""~=--...,__${red}\\/|\\/${white}__,...--=~""~~'-. ${white}( ..=${red}\\=${red}/${white}=.. ) ${white} `'-. ,.-"`;${red}/=\\${white};"-.,_ .-'` ${white} `~"-=-~` .-~` ${red}|=|${white} `~-. `~-=-"~` -${white} .-~` /${red}|=|${white}\ `~-. -${white} .~` / ${red}|=|${white} \ `~. +${white} .-~` /${red}|=|${white}\\ `~-. +${white} .~` / ${red}|=|${white} \\ `~. ${white} .-~` .' ${red}|=|${white} `. `~-. ${white} (` _,.-="` ${red} |=|${white} `"=-.,_ `) ${white} `~"~"` ${red} |=|${white} `"~"~` diff --git a/assets/ascii/gnu.txt b/assets/ascii/gnu.txt index e8c0fe3c..285559e6 100644 --- a/assets/ascii/gnu.txt +++ b/assets/ascii/gnu.txt @@ -1,14 +1,14 @@ ${1} _-`````-, ,- '- . ${1} .' .- - | | - -. `. -${1} /.' / `. \ +${1} /.' / `. \\ ${1}:/ : _... ..._ `` : -${1}:: : /._ .`:'_.._\. || : -${1}:: `._ ./ ,` : \ . _.'' . -${1}`:. / | -. \-. \\_ / -${1} \:._ _/ .' .@) \@) ` `\ ,.' -${1} _/,--' .- .\,-.`--`. -${1} ,'/'' (( \ ` ) -${1} /'/' \ `-' ( +${1}:: : /._ .`:'_.._\\. || : +${1}:: `._ ./ ,` : \\ . _.'' . +${1}`:. / | -. \\-. \\_ / +${1} \\:._ _/ .' .@) \\@) ` `\\ ,.' +${1} _/,--' .- .\\,-.`--`. +${1} ,'/'' (( \\ ` ) +${1} /'/' \\ `-' ( ${1} '/'' `._,-----' ${1} ''/' .,---' ${1} ''/' ;: diff --git a/assets/ascii/grombyang.txt b/assets/ascii/grombyang.txt index 51ffd671..05d20873 100644 --- a/assets/ascii/grombyang.txt +++ b/assets/ascii/grombyang.txt @@ -8,10 +8,10 @@ ${blue}eee ${green}`+oooooo: ${blue}eee ${blue}eee ${green}-+oooooo+: ${blue}eee ${blue}ee ${green}`/:oooooooo+: ${blue}ee ${blue}ee ${green}`/+ +++ +: ${blue}ee -${blue}ee ${green}+o+\ ${blue}ee -${blue}eee ${green}+o+\ ${blue}eee -${blue}eee ${green}// \\ooo/ \\\ ${blue}eee -${blue} eee ${green}//++++oooo++++\\\ ${blue}eee +${blue}ee ${green}+o+\\ ${blue}ee +${blue}eee ${green}+o+\\ ${blue}eee +${blue}eee ${green}// \\ooo/ \\\\ ${blue}eee +${blue} eee ${green}//++++oooo++++\\\\ ${blue}eee ${blue} eeee ${green}::::++oooo+::::: ${blue}eeee ${blue} eeeee ${red}Grombyang OS ${blue} eeee ${blue} eeeeeeeeeeeeeeeeeeeeeee diff --git a/assets/ascii/hydroos.txt b/assets/ascii/hydroos.txt index fb1e59eb..f6e45751 100644 --- a/assets/ascii/hydroos.txt +++ b/assets/ascii/hydroos.txt @@ -1,9 +1,9 @@ ${green}${red} ${red} _ _ _ ____ _____ -${red} | | | | | | / __ \ / ____| +${red} | | | | | | / __ \\ / ____| ${red} | |__| |_ _ __| |_ __ ___ | | | | (___ -${red} | __ | | | |/ _` | '__/ _ \| | | |\___ \ +${red} | __ | | | |/ _` | '__/ _ \\| | | |\\___ \\ ${red} | | | | |_| | (_| | | | (_) | |__| |____) | -${red} |_| |_|\__, |\__,_|_| \___/ \____/|_____/ +${red} |_| |_|\\__, |\\__,_|_| \\___/ \\____/|_____/ ${red} __/ | ${red} |___/ diff --git a/assets/ascii/linuxminix.txt b/assets/ascii/linuxminix.txt new file mode 100644 index 00000000..e618fca6 --- /dev/null +++ b/assets/ascii/linuxminix.txt @@ -0,0 +1,17 @@ +${red}${white} -sdhyo+:-` -/syymm: +${white} sdyooymmNNy. `` .smNmmdysNd +${white} odyoso+syNNmysoyhhdhsoomNmm+/osdm/ +${white} :hhy+-/syNNmddhddddddmNMNo:sdNd: +${white} `smNNdNmmNmddddddddddmmmmmmmy` +${white} `ohhhhdddddmmNNdmddNmNNmdddddmdh- +${white} odNNNmdyo/:/-/hNddNy-`..-+ydNNNmd: +${white} `+mNho:` smmd/ sNNh :dmms` -+ymmo. +${white}-od/ -m${red}mm${white}mo -NN+ +m${red}mm${white}m- yms: +${white}+sms -.` :so: .NN+ :os/ .-`mNh: +${white}.-hyh+:////- -sNNd:` .--://ohNs- +${white} `:hNNNNNNNMMd/sNMmhsdMMh/ymmNNNmmNNy/ +${white} -+sNNNNMMNNNsmNMo: :NNmymNNNNMMMms: +${white} //oydNMMMMydMMNysNMMmsMMMMMNyo/` +${white} ../-yNMMy--/::/-.sMMmos+.` +${white} -+oyhNsooo+omy/``` +${white} `::ohdmds-` diff --git a/assets/ascii/linuxmint.txt b/assets/ascii/linuxmint.txt new file mode 100644 index 00000000..cd69a502 --- /dev/null +++ b/assets/ascii/linuxmint.txt @@ -0,0 +1,19 @@ +{1} ...-:::::-... +${1} .-MMMMMMMMMMMMMMM-. +${1} .-MMMM${green}`..-:::::::-..`${1}MMMM-. +${1} .:MMMM${green}.:MMMMMMMMMMMMMMM:.${1}MMMM:. +${1} -MMM${green}-M---MMMMMMMMMMMMMMMMMMM.${1}MMM- +${1} `:MMM${green}:MM` :MMMM:....::-...-MMMM:${1}MMM:` +${1} :MMM${green}:MMM` :MM:` `` `` `:MMM:${1}MMM: +${1}.MMM${green}.MMMM` :MM. -MM. .MM- `MMMM.${1}MMM. +${1}:MMM${green}:MMMM` :MM. -MM- .MM: `MMMM-${1}MMM: +${1}:MMM${green}:MMMM` :MM. -MM- .MM: `MMMM:${1}MMM: +${1}:MMM${green}:MMMM` :MM. -MM- .MM: `MMMM-${1}MMM: +${1}.MMM${green}.MMMM` :MM:--:MM:--:MM: `MMMM.${1}MMM. +${1} :MMM${green}:MMM- `-MMMMMMMMMMMM-` -MMM-${1}MMM: +${1} :MMM${green}:MMM:` `:MMM:${1}MMM: +${1} .MMM${green}.MMMM:--------------:MMMM.${1}MMM. +${1} '-MMMM${green}.-MMMMMMMMMMMMMMM-.${1}MMMM-' +${1} '.-MMMM${green}``--:::::--``${1}MMMM-.' +${1} '-MMMMMMMMMMMMM-' +${1} ``-:::::-`` diff --git a/assets/ascii/netbsd_small.txt b/assets/ascii/netbsd_small.txt index 360814ae..2ff11e16 100644 --- a/assets/ascii/netbsd_small.txt +++ b/assets/ascii/netbsd_small.txt @@ -1,7 +1,7 @@ -${!#fc6906}${white}\\\\${!#fc6906}\`-______,----__ -${white} \\\\ ${!#fc6906}__,---\`_ -${white} \\\\ ${!#fc6906}\`.____ -${white} \\\\${!#fc6906}-______,----\`- +${!#fc6906}${white}\\\\${!#fc6906}\\`-______,----__ +${white} \\\\ ${!#fc6906}__,---\\`_ +${white} \\\\ ${!#fc6906}\\`.____ +${white} \\\\${!#fc6906}-______,----\\`- ${white} \\\\ ${white} \\\\ ${white} \\\\ diff --git a/assets/ascii/openbsd.txt b/assets/ascii/openbsd.txt index 9448c746..ebae7fc1 100644 --- a/assets/ascii/openbsd.txt +++ b/assets/ascii/openbsd.txt @@ -1,22 +1,22 @@ -${cyan} _ +${yellow}${cyan} _ ${cyan} (_) ${yellow} | . ${yellow} . |L /| . ${cyan} _ -${yellow} _ . |\ _| \--+._/| . ${cyan}(_) -${yellow} / ||\| Y J ) / |/| ./ +${yellow} _ . |\\ _| \\--+._/| . ${cyan}(_) +${yellow} / ||\\| Y J ) / |/| ./ ${yellow} J |)'( | ` F`.'/ ${cyan} _ ${yellow} -\<| F __ .-\< ${cyan}(_) ${yellow} | / .-'${cyan}. ${yellow}`. /${cyan}-. ${yellow}L___ -${yellow} J \\ \< ${cyan}\ ${yellow} | | ${black}O${cyan}\\${yellow}|.-' ${cyan} _ +${yellow} J \\ \< ${cyan}\\ ${yellow} | | ${black}O${cyan}\\${yellow}|.-' ${cyan} _ ${yellow} _J \\ .- \\${cyan}/ ${black}O ${cyan}| ${yellow}| \\ |${yellow}F ${cyan}(_) ${yellow} '-F -\<_. \\ .-' `-' L__ ${yellow}__J _ _. >-' )${red}._. ${yellow}|-' -${yellow} `-|.' /_. ${red}\_| ${yellow} F +${yellow} `-|.' /_. ${red}\\_| ${yellow} F ${yellow} /.- . _.\< ${yellow} /' /.' .' `\\ ${yellow} /L /' |/ _.-'-\\ -${yellow} /'J ___.---'\| -${yellow} |\ .--' V | `. ` +${yellow} /'J ___.---'\\| +${yellow} |\\ .--' V | `. ` ${yellow} |/`. `-. `._) ${yellow} / .-.\\ ${yellow} \\ ( `\\ diff --git a/assets/ascii/parsix.txt b/assets/ascii/parsix.txt index 7bb09876..41a02dec 100644 --- a/assets/ascii/parsix.txt +++ b/assets/ascii/parsix.txt @@ -13,7 +13,7 @@ ${white}-/:::::::::/:${black}`:-. .-:${white}`:///////////- `${white}-::::--${yellow}.-://.${black}---....---${yellow}`:+/:-${white}--::::-` ${yellow}-/+///+o/-${black}.----.${yellow}.:oo+++o+. ${yellow}-+/////+++o:${red}syyyyy.${yellow}o+++++++++: - ${yellow}.+////+++++-${red}+sssssy+${yellow}.++++++++++\ + ${yellow}.+////+++++-${red}+sssssy+${yellow}.++++++++++\\ ${yellow}.+:/++++++.${red}.yssssssy-${yellow}`+++++++++: ${yellow}:/+++++- ${red}+sssssssss ${yellow}-++++++- ${yellow}`--` ${red}+sssssssso ${yellow}`--` diff --git a/assets/ascii/ubuntu.txt b/assets/ascii/ubuntu.txt index 09be9e0d..b213976f 100644 --- a/assets/ascii/ubuntu.txt +++ b/assets/ascii/ubuntu.txt @@ -1,20 +1,20 @@ -${red} .-/+oossssoo+\-. +${red} .-/+oossssoo+\\-. ${red} `:+ssssssssssssssssss+:` ${red} -+ssssssssssssssssssyyssss+- ${red} .ossssssssssssssssss${white}dMMMNy${red}sssso. -${red} /sssssssssss${white}hdmmNNmmyNMMMMh${red}ssssss\ +${red} /sssssssssss${white}hdmmNNmmyNMMMMh${red}ssssss\\ ${red} +sssssssss${white}hm${red}yd${white}MMMMMMMNddddy${red}ssssssss+ -${red} /ssssssss${white}hNMMM${red}yh${white}hyyyyhmNMMMNh${red}ssssssss\ +${red} /ssssssss${white}hNMMM${red}yh${white}hyyyyhmNMMMNh${red}ssssssss\\ ${red}.ssssssss${white}dMMMNh${red}ssssssssss${white}hNMMMd${red}ssssssss. ${red}+ssss${white}hhhyNMMNy${red}ssssssssssss${white}yNMMMy${red}sssssss+ ${red}oss${white}yNMMMNyMMh${red}ssssssssssssss${white}hmmmh${red}ssssssso ${red}oss${white}yNMMMNyMMh${red}sssssssssssssshmmmh${red}ssssssso ${red}+ssss${white}hhhyNMMNy${red}ssssssssssss${white}yNMMMy${red}sssssss+ ${red}.ssssssss${white}dMMMNh${red}ssssssssss${white}hNMMMd${red}ssssssss. -${red} \ssssssss${white}hNMMM${red}yh${white}hyyyyhdNMMMNh${red}ssssssss/ +${red} \\ssssssss${white}hNMMM${red}yh${white}hyyyyhdNMMMNh${red}ssssssss/ ${red} +sssssssss${white}dm${red}yd${white}MMMMMMMMddddy${red}ssssssss+ -${red} \sssssssssss${white}hdmNNNNmyNMMMMh${red}ssssss/ +${red} \\sssssssssss${white}hdmNNNNmyNMMMMh${red}ssssss/ ${red} .ossssssssssssssssss${white}dMMMNy${red}sssso. ${red} -+sssssssssssssssss${white}yyy${red}ssss+- ${red} `:+ssssssssssssssssss+:` -${red} .-\+oossssoo+/-. +${red} .-\\+oossssoo+/-. diff --git a/assets/icons/Thumbnail.png b/assets/icons/Thumbnail.png new file mode 100644 index 00000000..33dc65f7 Binary files /dev/null and b/assets/icons/Thumbnail.png differ diff --git a/assets/icons/Thumbnail.svg b/assets/icons/Thumbnail.svg new file mode 100644 index 00000000..e40c48c4 --- /dev/null +++ b/assets/icons/Thumbnail.svg @@ -0,0 +1,391 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + viewBox="0 0 400 300" + version="1.1" + id="svg32" + sodipodi:docname="logo2.svg" + inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs32"> + <clipPath + id="gravityUiGpu0-6"> + <path + fill="currentColor" + d="M 0,0 H 16 V 16 H 0 Z" + id="path2-5" /> + </clipPath> + <clipPath + id="clipPath54"> + <path + fill="currentColor" + d="M 0,0 H 16 V 16 H 0 Z" + id="path54" /> + </clipPath> + </defs> + <sodipodi:namedview + id="namedview32" + pagecolor="#ffffff" + bordercolor="#999999" + borderopacity="1" + inkscape:showpageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:zoom="1.4672466" + inkscape:cx="232.74888" + inkscape:cy="141.42136" + inkscape:window-width="1322" + inkscape:window-height="724" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" + inkscape:current-layer="svg32" /> + <!-- Terminal window background --> + <rect + x="20" + y="20" + width="360" + height="260" + rx="8" + fill="#1f2937" + stroke="#374151" + stroke-width="2" + id="rect1" /> + <g + fill="none" + id="g2" + transform="matrix(0.86888851,0,0,1.0183547,35.021598,149.44004)" + style="fill:#10b981;fill-opacity:1"> + <path + d="m 15.65458,30.590304 v 0.679963 c 0,0.250274 -0.344743,0.452303 -0.771815,0.452303 h -1.16286 c -0.427071,0 -0.771815,-0.202029 -0.771815,-0.452303 v -0.679963 c 0,-0.250274 0.344744,-0.452303 0.771815,-0.452303 h 1.160287 c 0.427072,0 0.774388,0.203537 0.774388,0.452303 z" + fill="#292d32" + id="path14-3" + style="fill:#4e064e;fill-opacity:1;stroke-width:0.196948" + transform="matrix(4.8993377,0,0,5.9633617,-60.911942,-176.44217)" /> + <g + clip-path="url(#gravityUiGpu0-6)" + id="g1" + style="fill:#ffff00;fill-opacity:1"> + <path + fill="currentColor" + fill-rule="evenodd" + d="M 0,1.25 A 0.75,0.75 0 0 1 0.75,0.5 h 0.5 A 2.25,2.25 0 0 1 3.5,2.75 V 3 H 13 a 3,3 0 0 1 3,3 v 4 a 3,3 0 0 1 -3,3 H 3.5 v 1.25 a 0.75,0.75 0 0 1 -1.5,0 V 2.75 A 0.75,0.75 0 0 0 1.25,2 H 0.75 A 0.75,0.75 0 0 1 0,1.25 M 13,4.5 H 3.5 v 7 H 13 A 1.5,1.5 0 0 0 14.5,10 V 6 A 1.5,1.5 0 0 0 13,4.5 M 8.5,8 a 1,1 0 1 1 -2,0 1,1 0 0 1 2,0 M 10,8 a 2.5,2.5 0 1 1 -5,0 2.5,2.5 0 0 1 5,0" + clip-rule="evenodd" + id="path1-3" + style="fill:#ff5eff;fill-opacity:0.95096982;opacity:0.88458041" /> + </g> + <defs + id="defs2"> + <clipPath + id="clipPath32"> + <path + fill="currentColor" + d="M 0,0 H 16 V 16 H 0 Z" + id="path32" /> + </clipPath> + </defs> + </g> + <!-- Window title bar --> + <rect + x="20" + y="20" + width="360" + height="35" + rx="8" + fill="#374151" + id="rect2" /> + <rect + x="20" + y="51" + width="360" + height="8" + fill="#374151" + id="rect3" /> + <!-- Window controls --> + <circle + cx="40" + cy="37.5" + r="6" + fill="#ef4444" + id="circle3" /> + <circle + cx="60" + cy="37.5" + r="6" + fill="#f59e0b" + id="circle4" /> + <circle + cx="80" + cy="37.5" + r="6" + fill="#10b981" + id="circle5" /> + <!-- Window title --> + <text + x="200" + y="42" + font-family="monospace" + font-size="12" + fill="#9ca3af" + text-anchor="middle" + id="text5">customfetch</text> + <!-- Terminal prompt --> + <text + x="34.518074" + y="80.96386" + font-family="monospace" + font-size="14px" + fill="#10b981" + id="text6" + style="font-size:16px">></text> + <text + x="50" + y="80" + font-family="monospace" + font-size="14" + fill="#e5e7eb" + id="text7">customfetch</text> + <!-- System info layout --> + <g + transform="translate(35, 100)" + id="g29"> + <!-- OS Info --> + <g + id="g9"> + <circle + cx="7.3975902" + cy="8.1204815" + r="6" + fill="#2563eb" + id="circle7" /> + <text + x="20" + y="12" + font-family="monospace" + font-size="12" + fill="#9ca3af" + id="text8">OS</text> + <text + x="100" + y="12" + font-family="monospace" + font-size="12" + fill="#e5e7eb" + id="text9">Linux</text> + </g> + <!-- CPU Info --> + <g + transform="translate(0, 25)" + id="g15"> + <!-- CPU icon --> + <g + id="SVGRepo_iconCarrier-0-8" + transform="matrix(0.74625,0,0,0.75135,-2.2306322,-0.96849988)" + style="fill:#10b981;fill-opacity:1" + inkscape:label="cpu " + inkscape:transform-center-x="-17.831325" + inkscape:transform-center-y="5.7831325"> + <path + opacity="0.4" + d="M 15,4 H 9 C 6.24,4 4,6.24 4,9 v 6 c 0,2.76 2.24,5 5,5 h 6 c 2.76,0 5,-2.24 5,-5 V 9 C 20,6.24 17.76,4 15,4 Z m 2.26,10.26 c 0,1.66 -1.34,3 -3,3 H 9.74 c -1.66,0 -3,-1.34 -3,-3 V 9.74 c 0,-1.66 1.34,-3 3,-3 h 4.51 c 1.66,0 3,1.34 3,3 v 4.52 z" + fill="#292d32" + id="path1-9-1" + style="fill:#10b981;fill-opacity:1" /> + <path + d="M 9.06055,2.75 V 4 h -0.06 c -0.5,0 -0.98,0.07 -1.44,0.21 V 2.75 c 0,-0.41 0.33,-0.75 0.75,-0.75 0.41,0 0.75,0.34 0.75,0.75 z" + fill="#292d32" + id="path2-3-0" + style="fill:#10b981;fill-opacity:1" /> + <path + d="M 12.75,2.75 V 4 h -1.5 V 2.75 C 11.25,2.34 11.59,2 12,2 c 0.41,0 0.75,0.34 0.75,0.75 z" + fill="#292d32" + id="path3-6-3" + style="fill:#10b981;fill-opacity:1" /> + <path + d="M 16.4492,2.75 V 4.21 C 15.9892,4.07 15.4992,4 14.9992,4 h -0.05 V 2.75 c 0,-0.41 0.34,-0.75 0.75,-0.75 0.41,0 0.75,0.34 0.75,0.75 z" + fill="#292d32" + id="path4-0-0" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 21.9991,8.30005 c 0,0.42 -0.33,0.75 -0.75,0.75 h -1.25 v -0.05 c 0,-0.5 -0.07,-0.99 -0.21,-1.45 h 1.46 c 0.42,0 0.75,0.34 0.75,0.75 z" + fill="#292d32" + id="path5-6-4" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 22,12 c 0,0.41 -0.33,0.75 -0.75,0.75 H 20 v -1.5 h 1.25 c 0.42,0 0.75,0.33 0.75,0.75 z" + fill="#292d32" + id="path6-4" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 21.9991,15.7 c 0,0.41 -0.33,0.75 -0.75,0.75 h -1.46 c 0.14,-0.46 0.21,-0.95 0.21,-1.45 v -0.05 h 1.25 c 0.42,0 0.75,0.33 0.75,0.75 z" + fill="#292d32" + id="path7-4" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 16.4492,19.79 v 1.46 c 0,0.41 -0.34,0.75 -0.75,0.75 -0.41,0 -0.75,-0.34 -0.75,-0.75 V 20 h 0.05 c 0.5,0 0.99,-0.07 1.45,-0.21 z" + fill="#292d32" + id="path8-4" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 12.7598,20 v 1.25 c 0,0.41 -0.34,0.75 -0.75,0.75 -0.42,0 -0.75,-0.34 -0.75,-0.75 V 20 Z" + fill="#292d32" + id="path9-7" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 9.06055,20 v 1.25 c 0,0.41 -0.34,0.75 -0.75,0.75 -0.42,0 -0.75,-0.34 -0.75,-0.75 v -1.46 c 0.46,0.14 0.94,0.21 1.44,0.21 z" + fill="#292d32" + id="path10-6" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 4.21,7.55005 c -0.14,0.46 -0.21,0.95 -0.21,1.45 v 0.05 H 2.75 c -0.41,0 -0.75,-0.33 -0.75,-0.75 0,-0.41 0.34,-0.75 0.75,-0.75 z" + fill="#292d32" + id="path11-3" + style="fill:#10b981;fill-opacity:1" /> + <path + d="m 4,11.25 v 1.5 H 2.75 C 2.34,12.75 2,12.41 2,12 2,11.58 2.34,11.25 2.75,11.25 Z" + fill="#292d32" + id="path12-1" + style="fill:#10b981;fill-opacity:1" /> + <path + d="M 4.21,16.45 H 2.75 C 2.34,16.45 2,16.11 2,15.7 2,15.28 2.34,14.95 2.75,14.95 H 4 V 15 c 0,0.5 0.07,0.99 0.21,1.45 z" + fill="#292d32" + id="path13-7" + style="fill:#10b981;fill-opacity:1" /> + <path + d="M 17.2602,9.73999 V 14.25 c 0,1.66 -1.34,3 -3,3 H 9.74023 c -1.66,0 -3,-1.34 -3,-3 V 9.73999 c 0,-1.66 1.34,-3 3,-3 h 4.50997 c 1.66,0 3.01,1.35 3.01,3 z" + fill="#292d32" + id="path14-5" + style="fill:#10b981;fill-opacity:1" /> + </g> + <text + x="20" + y="12" + font-family="monospace" + font-size="12" + fill="#9ca3af" + id="text14">CPU</text> + <text + x="100" + y="12" + font-family="monospace" + font-size="12" + fill="#e5e7eb" + id="text15">Intel i9-10900K</text> + </g> + <!-- GPU Info --> + <g + transform="translate(0, 50)" + id="g21"> + <!-- GPU icon --> + <text + x="20" + y="12" + font-family="monospace" + font-size="12" + fill="#9ca3af" + id="text20">GPU</text> + <text + x="100" + y="12" + font-family="monospace" + font-size="12" + fill="#e5e7eb" + id="text21">RTX 4090</text> + </g> + <!-- Memory Info --> + <g + transform="translate(0, 75)" + id="g26"> + <!-- Memory icon --> + <g + id="SVGRepo_iconCarrier" + transform="matrix(0.61388235,0,0,0.70055055,-0.32931007,-0.13603532)" + style="fill:#800080;fill-opacity:1"> + <path + d="m 16.0103,20.97 c 0.0056,0.5561 -0.4437,1.01 -0.9999,1.01 H 8.99374 c -0.54968,0 -0.99631,-0.4436 -0.99998,-0.9933 L 7.98047,19 c 0,-1.1 0.89,-2 2,-2 h 4.01003 c 1.1,0 1.99,0.89 2,1.99 z" + fill="#292d32" + id="path1" + style="fill:#800080;fill-opacity:1" /> + <path + d="m 18.5,9.99938 v 2.17002 c 0,0.53 0.21,1.04 0.59,1.42 l 0.82,0.82 c 0.38,0.38 0.59,0.89 0.59,1.42 v 1.17 c 0,2.3011 -0.9089,3.9936 -2.8943,4.6661 -0.3012,0.1021 -0.5968,-0.1358 -0.5979,-0.4538 L 17,18.9794 c -0.02,-1.64 -1.37,-2.98 -3,-2.98 H 9.99 c -1.66,0 -3,1.35 -3,3 v 2.2241 c 0,0.3175 -0.29371,0.5563 -0.5951,0.4566 C 4.386,21.0153 3.5,19.3124 3.5,16.9994 V 6.99938 C 3.5,4.59334 4.45179,2.83316 6.64523,2.23589 6.94579,2.15404 7.2259,2.3956 7.22143,2.70706 L 7.19,4.89938 c -0.01,0.47 0.16,0.92 0.48,1.26 0.33,0.34 0.76,0.52999 1.27,0.54 0.51417,0 0.97203,-0.21276 1.2845,-0.55906 0.1122,-0.12438 0.3271,-0.12939 0.4455,-0.01094 0.32,0.34 0.76,0.53999 1.27,0.54999 0.5143,0 0.9835,-0.22605 1.3036,-0.58992 0.1052,-0.11957 0.3206,-0.12911 0.4264,-0.01007 0.32,0.33999 0.76,0.54 1.27,0.55 0.96,0 1.73,-0.75001 1.75,-1.72001 L 16.7297,2.68602 C 16.7351,2.38172 17.0106,2.15075 17.3047,2.22893 19.5087,2.8147 20.5,4.58656 20.5,6.99938 v 1 c 0,0.55 -0.45,1 -1,1 -0.55,0 -1,0.45 -1,1 z" + fill="#292d32" + id="path2" + style="fill:#800080;fill-opacity:1" /> + <path + d="M 9.22788,2.0033 C 9.50858,2.00149 9.73577,2.23107 9.73103,2.51174 L 9.68967,4.96 c -0.01,0.41 -0.34,0.74 -0.75,0.74 H 8.92968 C 8.50968,5.69 8.17967,5.35 8.18967,4.93 V 2.50678 c 0,-0.27488 0.2219,-0.49821 0.49678,-0.49999 z" + fill="#292d32" + id="path3" + style="fill:#800080;fill-opacity:1" /> + <path + d="m 12.2311,2 c 0.2795,0 0.5047,0.22906 0.5,0.50847 L 12.6897,4.95 c -0.01,0.4 -0.34,0.73 -0.75,0.73 h -0.01 c -0.42,-0.01 -0.75,-0.35 -0.74,-0.76 V 2.5 c 0,-0.27614 0.2238,-0.5 0.5,-0.5 z" + fill="#292d32" + id="path4" + style="fill:#800080;fill-opacity:1" /> + <path + d="m 15.2341,2.00658 c 0.2783,0.00175 0.5016,0.23041 0.4968,0.50867 l -0.0412,2.37452 c -0.01,0.41 -0.34,0.74 -0.75,0.74 h -0.01 c -0.42,-0.01 -0.75,-0.35 -0.74,-0.76 V 2.50316 c 0,-0.27737 0.2258,-0.50174 0.5031,-0.49999 z" + fill="#292d32" + id="path5" + style="fill:#800080;fill-opacity:1" /> + </g> + <text + x="20" + y="12" + font-family="monospace" + font-size="12" + fill="#9ca3af" + id="text25">Memory</text> + <text + x="100" + y="12" + font-family="monospace" + font-size="12" + fill="#e5e7eb" + id="text26">32GB DDR4</text> + </g> + <!-- Uptime --> + <g + transform="translate(0, 100)" + id="g28"> + <circle + cx="8" + cy="8" + r="6" + fill="#ef4444" + id="circle26" /> + <path + d="M8 4 L8 8 L11 11" + stroke="#1f2937" + stroke-width="1.5" + stroke-linecap="round" + fill="none" + id="path26" /> + <text + x="20" + y="12" + font-family="monospace" + font-size="12" + fill="#9ca3af" + id="text27">Uptime</text> + <text + x="100" + y="12" + font-family="monospace" + font-size="12" + fill="#e5e7eb" + id="text28">2h 34m</text> + </g> + </g> + <!-- Cursor --> + <rect + x="35" + y="250" + width="8" + height="16" + fill="#10b981" + id="rect29" /> + <!-- Subtle logo watermark --> +</svg> diff --git a/assets/icons/logo.png b/assets/icons/logo.png new file mode 100644 index 00000000..dcbddfd4 Binary files /dev/null and b/assets/icons/logo.png differ diff --git a/assets/icons/logo.svg b/assets/icons/logo.svg new file mode 100644 index 00000000..17896faf --- /dev/null +++ b/assets/icons/logo.svg @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + width="256" + height="256" + viewBox="0 0 128 128" + fill="none" + version="1.1" + id="svg4" + sodipodi:docname="logoLinux.svg" + inkscape:version="1.4.2 (ebf0e94, 2025-05-08)" + inkscape:export-filename="logoLinux.png" + inkscape:export-xdpi="96" + inkscape:export-ydpi="96" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="namedview4" + pagecolor="#ffffff" + bordercolor="#999999" + borderopacity="1" + inkscape:showpageshadow="2" + inkscape:pageopacity="0" + inkscape:pagecheckerboard="0" + inkscape:deskcolor="#d1d1d1" + inkscape:zoom="1.5440339" + inkscape:cx="114.95862" + inkscape:cy="130.50232" + inkscape:window-width="1900" + inkscape:window-height="1042" + inkscape:window-x="10" + inkscape:window-y="28" + inkscape:window-maximized="1" + inkscape:current-layer="svg4" + showgrid="false" /> + <rect + width="128" + height="128" + rx="12" + fill="#1E1E1E" + id="rect1" /> + <g + id="SVGRepo_bgCarrier-5" + stroke-width="0" + transform="matrix(4.9999896,0,0,5,58.5,73.5)" /> + <g + id="SVGRepo_tracerCarrier-3" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(4.9999896,0,0,5,58.5,73.5)" /> + <g + id="SVGRepo_iconCarrier" + transform="matrix(4.3739252,0,0,3.9853418,30.974698,67.671113)" + style="fill:#00ffb3;fill-opacity:1"> + <path + d="M 5,8 V 7 h 1 v 1 z" + fill="#000000" + id="path1" + style="fill:#00ffb3;fill-opacity:1" /> + <path + d="M 9,8 V 7 h 1 v 1 z" + fill="#000000" + id="path2" + style="fill:#00ffb3;fill-opacity:1" /> + <path + fill-rule="evenodd" + clip-rule="evenodd" + d="m 1.00001,6.5 c 0,-3.58985 2.91015,-6.5 6.5,-6.5 C 11.0899,0 14,2.91015 14,6.5 v 6.514 c 0,0.1786 0.0709,0.3498 0.1972,0.4761 l 0.6564,0.6563 c 0.143,0.143 0.1857,0.3581 0.1084,0.5449 C 14.8846,14.8782 14.7022,15 14.5,15 H 0.500015 C 0.297783,15 0.115465,14.8782 0.0380749,14.6913 -0.0393156,14.5045 0.00346218,14.2894 0.146461,14.1464 l 0.65638,-0.6563 C 0.929089,13.3638 1.00001,13.1926 1.00001,13.014 Z M 4,6.5 C 4,5.67157 4.67157,5 5.5,5 6.32843,5 7,5.67157 7,6.5 v 1 C 7,8.32843 6.32843,9 5.5,9 4.67157,9 4,8.32843 4,7.5 Z m 4,0 C 8,5.67157 8.67157,5 9.5,5 10.3284,5 11,5.67157 11,6.5 v 1 C 11,8.32843 10.3284,9 9.5,9 8.67157,9 8,8.32843 8,7.5 Z m -3.40698,4.0125 c 1.82993,-0.91495 3.98385,-0.91495 5.81378,0 l 0.249,0.1245 -0.0952,0.0953 c -0.81177,0.8117 -1.91272,1.2678 -3.06069,1.2678 -1.14797,0 -2.24892,-0.4561 -3.06066,-1.2678 L 4.34399,10.637 Z" + fill="#000000" + id="path3" + style="fill:#00ffb3;fill-opacity:1" /> + </g> + <g + id="SVGRepo_bgCarrier" + stroke-width="0" + transform="matrix(0.5,0,0,0.5,-85.542325,38.095677)" + style="fill:#07ca90;fill-opacity:1" /> + <g + id="SVGRepo_tracerCarrier" + stroke-linecap="round" + stroke-linejoin="round" + transform="matrix(0.5,0,0,0.5,-85.542325,38.095677)" + style="fill:#07ca90;fill-opacity:1" /> + <rect + width="128" + height="22.898033" + rx="12" + fill="#1e1e1e" + id="rect1-7" + x="-0.1529517" + y="0.39355993" + ry="10.909091" + style="fill:#333333;fill-opacity:1;stroke-width:0.40125" /> + <rect + x="0.32826477" + y="14.793382" + width="127.7925" + height="8.8085871" + fill="#374151" + id="rect3" + style="fill:#333333;fill-opacity:1;stroke-width:0.625186" + inkscape:label="rect3" /> + <circle + cx="12" + cy="11" + r="4" + fill="#ff5f56" + id="circle2" /> + <circle + cx="24" + cy="11" + r="4" + fill="#ffbd2e" + id="circle3" /> + <circle + cx="36" + cy="11" + r="4" + fill="#27c93f" + id="circle4" /> + <text + x="64.35788" + y="43.019432" + dominant-baseline="middle" + text-anchor="middle" + font-family="monospace" + font-size="36px" + fill="#00ffb3" + id="text4" + style="font-size:14.2672px;stroke-width:0.764315">>CustomFetch_</text> +</svg> diff --git a/cmake_uninstall.cmake.in b/cmake_uninstall.cmake.in new file mode 100644 index 00000000..5a099b74 --- /dev/null +++ b/cmake_uninstall.cmake.in @@ -0,0 +1,37 @@ +message(STATUS "Removing special files") +file(REMOVE "${CMAKE_INSTALL_PREFIX}/bin/cufetch") # Symlink +if(EXISTS "${CMAKE_INSTALL_PREFIX}/bin/customfetch-gui") + file(REMOVE "${CMAKE_INSTALL_PREFIX}/bin/customfetch-gui") # GUI executable +endif() + +if(EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(STATUS "Processing install manifest") + file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) + string(REGEX REPLACE "\n" ";" files "${files}") + + foreach(file ${files}) + if(EXISTS "${file}" OR IS_SYMLINK "${file}") + message(STATUS "Removing: ${file}") + file(REMOVE "${file}") + + # Remove empty parent directories + get_filename_component(dir "${file}" DIRECTORY) + while(NOT dir STREQUAL "${CMAKE_INSTALL_PREFIX}") + file(GLOB contents "${dir}/*") + if(NOT contents) + message(STATUS "Removing empty directory: ${dir}") + file(REMOVE_RECURSE "${dir}") + get_filename_component(dir "${dir}" DIRECTORY) + else() + break() + endif() + endwhile() + else() + message(STATUS "File not found (may be already removed): ${file}") + endif() + endforeach() +else() + message(WARNING "Install manifest not found - some files may remain") +endif() + +message(STATUS "Uninstallation complete") diff --git a/compile_flags.txt b/compile_flags.txt index b8a11c53..b96efa0d 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,17 +1,17 @@ -I./include +-I./include/libs -Wall -Wextra -Wpedantic -std=c++20 --DVERSION="1.0.0" --DBRANCH="main" +-DVERSION="2.0.0-beta1" -DGUI_APP=1 -DUSE_DCONF=1 -DDEBUG=1 --DANDROID_APP=0 -DENABLE_NLS=1 -DPREFIX="/usr" -DLOCALEDIR="/usr/share/locale" +-DICONPREFIX="/usr/share/pixmaps/customfetch" -I./useless_stuff/ -I/usr/include/gtkmm-3.0 -I/usr/lib/gtkmm-3.0/include diff --git a/cufetchpm/Makefile b/cufetchpm/Makefile new file mode 100644 index 00000000..0aeee4bd --- /dev/null +++ b/cufetchpm/Makefile @@ -0,0 +1,73 @@ +CXX ?= g++ +PREFIX ?= /usr +MANPREFIX ?= $(PREFIX)/share/man +VARS ?= -DENABLE_NLS=1 +CXXSTD ?= c++20 + +DEBUG ?= 1 +# https://stackoverflow.com/a/1079861 +# WAY easier way to build debug and release builds +ifeq ($(DEBUG), 1) + BUILDDIR = build/debug + CXXFLAGS := -ggdb3 -Wall -Wextra -pedantic -Wno-unused-parameter -fsanitize=address \ + -DDEBUG=1 -fno-omit-frame-pointer $(DEBUG_CXXFLAGS) $(CXXFLAGS) + LDFLAGS += -fsanitize=address -fno-lto +else + # Check if an optimization flag is not already set + ifneq ($(filter -O%,$(CXXFLAGS)),) + $(info Keeping the existing optimization flag in CXXFLAGS) + else + CXXFLAGS := -O3 $(CXXFLAGS) + endif + BUILDDIR = build/release +endif + +NAME = cufetchpm +TARGET ?= $(NAME) +OLDVERSION = 0.0.0 +VERSION = 0.0.1 +SRC = $(wildcard src/*.cpp ../src/util.cpp) +OBJ = $(SRC:.cpp=.o) +LDLIBS += $(BUILDDIR)/libfmt.a $(BUILDDIR)/libtiny-process-library.a +CXXFLAGS ?= -mtune=generic -march=native +CXXFLAGS += -fvisibility=hidden -I../include -I../include/libs/ -Iinclude -std=c++20 $(VARS) -DVERSION=\"$(VERSION)\" + +all: fmt toml getopt-port tpl $(TARGET) + +fmt: +ifeq ($(wildcard $(BUILDDIR)/libfmt.a),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/fmt BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +toml: +ifeq ($(wildcard $(BUILDDIR)/toml.o),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/toml++ BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +getopt-port: +ifeq ($(wildcard $(BUILDDIR)/getopt.o),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/getopt_port BUILDDIR=cufetchpm/$(BUILDDIR) +endif + +tpl: +ifeq ($(wildcard $(BUILDDIR)/libtiny-process-library.a),) + mkdir -p $(BUILDDIR) + make -C ../src/libs/tiny-process-library BUILDDIR=cufetchpm/$(BUILDDIR) CXXSTD=$(CXXSTD) +endif + +$(TARGET): fmt toml getopt-port tpl $(OBJ) + mkdir -p $(BUILDDIR) + $(CXX) $(OBJ) $(BUILDDIR)/*.o -o $(BUILDDIR)/$(TARGET) $(LDFLAGS) $(LDLIBS) + +clean: + rm -rf $(BUILDDIR)/$(TARGET) $(OBJ) + +distclean: + find .. -type f -name "*.tar.gz" -exec rm -rf "{}" \; + find .. -type f -name "*.o" -exec rm -rf "{}" \; + find .. -type f -name "*.a" -exec rm -rf "{}" \; + +.PHONY: $(TARGET) fmt toml all diff --git a/cufetchpm/compile_flags.txt b/cufetchpm/compile_flags.txt new file mode 100644 index 00000000..abd51790 --- /dev/null +++ b/cufetchpm/compile_flags.txt @@ -0,0 +1,13 @@ +-I../include +-I../include/libs +-I../src/libs/ +-Iinclude +-Wall +-Wextra +-Wpedantic +-std=c++20 +-DVERSION="1.0.0" +-DGUI_APP=1 +-DUSE_DCONF=1 +-DDEBUG=1 +-DENABLE_NLS=1 diff --git a/cufetchpm/include/manifest.hpp b/cufetchpm/include/manifest.hpp new file mode 100644 index 00000000..3c47d76f --- /dev/null +++ b/cufetchpm/include/manifest.hpp @@ -0,0 +1,156 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _MANIFEST_HPP_ +#define _MANIFEST_HPP_ + +#include <filesystem> +#include <string> +#include <string_view> +#include <vector> + +#include "platform.hpp" +#include "toml++/toml.hpp" + +#if CF_LINUX +constexpr char const PLATFORM[] = "linux"; +#elif CF_MACOS +constexpr char const PLATFORM[] = "macos"; +#elif CF_ANDROID +constexpr char const PLATFORM[] = "android"; +#endif + +struct plugin_t +{ + // The plugin name. + // It must be conform to the function is_valid_name() + std::string name; + + // The plugin description. + std::string description; + + // The plugin build directory, + // where we'll retrive the built plugin libraries. + std::string output_dir; + + // The plugin multiple SPDX License Identifier (MIT, GPL-2.0, ...) + // NOTE: it doesn't actually check if they are correct or not. + std::vector<std::string> licenses; + + // The plugin authors. + std::vector<std::string> authors; + + // A list of commands to be executed for building the plugin. + // Kinda like a Makefile target instructions. + // Each command will be executed from a different shell session. + std::vector<std::string> build_steps; + + // A list of registered root modules that the plugin will be used for querying its submodules. + // For example: 'github.followers' the root module is indeed 'github' and 'followers' is the submodule. + std::vector<std::string> prefixes; + + // Platforms that are supported by the plugin. + // Make it a string and put 'all' for being cross-platform. + std::vector<std::string> platforms; +}; + +struct manifest_t +{ + // The repository name. + // It must be conform to the function is_valid_name() + std::string name; + + // The repository git url + std::string url; + + // NOTE: INTERNAL ONLY + // The repository latest commit hash. + std::string git_hash; + + // An array of all the plugins that are declared in the manifest + std::vector<plugin_t> plugins; + + // An array for storing the dependencies for 'all' and current platforms. + // first -> platform string name + // seconds -> platform dependencies vector names + std::vector<std::string> dependencies; +}; + +constexpr char const MANIFEST_NAME[] = "cufetchpm.toml"; + +namespace ManifestSpace +{ +std::string getStrValue(const toml::table& tbl, const std::string_view name, const std::string_view key); +std::string getStrValue(const toml::table& tbl, const std::string_view path); +std::vector<std::string> getStrArrayValue(const toml::table& tbl, const std::string_view name, + const std::string_view value); +std::vector<std::string> getStrArrayValue(const toml::table& tbl, const std::string_view path); +} // namespace ManifestSpace + +class CManifest +{ +public: + CManifest(const std::filesystem::path& path); + + plugin_t get_plugin(const std::string_view name); + + const std::string& get_repo_name() const + { return m_repo.name; } + + const std::string& get_repo_url() const + { return m_repo.url; } + + const std::string& get_repo_hash() const + { return m_repo.git_hash; } + + const std::vector<plugin_t>& get_all_plugins() const + { return m_repo.plugins; } + + const std::vector<std::string>& get_dependencies() const + { return m_repo.dependencies; } + +private: + toml::table m_tbl; + manifest_t m_repo; + + void parse_manifest(const std::filesystem::path& path); + + std::string getStrValue(const std::string_view name, const std::string_view key) const + { + return ManifestSpace::getStrValue(m_tbl, name, key); + } + + std::string getStrValue(const std::string_view path) const + { + return ManifestSpace::getStrValue(m_tbl, path); + } + + std::vector<std::string> getStrArrayValue(const std::string_view name, const std::string_view value) const + { + return ManifestSpace::getStrArrayValue(m_tbl, name, value); + } +}; + +#endif // !_MANIFEST_HPP_; diff --git a/cufetchpm/include/pluginManager.hpp b/cufetchpm/include/pluginManager.hpp new file mode 100644 index 00000000..9b2b871c --- /dev/null +++ b/cufetchpm/include/pluginManager.hpp @@ -0,0 +1,78 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _PLUGIN_MANAGER_HPP_ +#define _PLUGIN_MANAGER_HPP_ + +#include <filesystem> +#include <string> +#include <string_view> +#include <utility> + +#include "stateManager.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +inline struct operations_t +{ + bool install_force = false; + bool install_shut_up = false; + bool list_verbose = false; + std::vector<std::string> arguments; +} options; + +template <typename... Args> +void success(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print("\033[1;32m==> {}\033[0m\n", fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +template <typename... Args> +void status(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print("\033[1;34m==> {} ...\033[0m\n", fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +class PluginManager +{ +public: + PluginManager(const StateManager& state_manager) : m_state_manager(state_manager) {} + PluginManager(StateManager&& state_manager) : m_state_manager(std::move(state_manager)) {} + + void add_plugins_repo(const std::string& repo); + void build_plugins(const fs::path& working_dir); + bool add_plugin(const std::string&); + void update_repos(); + bool is_plugin_conflicting(const plugin_t& plugin); + void remove_repo(const std::string& repo_name); + +private: + StateManager m_state_manager; + fs::path m_config_path{ getConfigDir() / "plugins" }; + fs::path m_cache_path{ getHomeCacheDir() / "cufetchpm" }; +}; + +#endif diff --git a/cufetchpm/include/stateManager.hpp b/cufetchpm/include/stateManager.hpp new file mode 100644 index 00000000..a5d25a95 --- /dev/null +++ b/cufetchpm/include/stateManager.hpp @@ -0,0 +1,91 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _STATE_MANAGER_HPP_ +#define _STATE_MANAGER_HPP_ + +#include <filesystem> + +#include "manifest.hpp" +#include "toml++/toml.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +bool writeState(const std::string& str, const std::string& to); +class StateManager +{ +public: + StateManager(); + StateManager(StateManager&&) = default; + StateManager(const StateManager&) = default; + ~StateManager() = default; + + void add_new_repo(const CManifest& manifest); + void remove_repo(const std::string& repo); + std::vector<manifest_t> get_all_repos(); + + template <typename T> + void insert_or_assign_at_plugin(const std::string_view repo_name, const std::string_view plugin_name, + const std::string_view key, T&& value); + + const toml::table& get_state() const + { return m_state; } + +private: + const fs::path m_path{ getConfigDir() / "plugins" / "state.toml" }; + toml::table m_state; +}; + +template <typename T> +void StateManager::insert_or_assign_at_plugin(const std::string_view repo_name, const std::string_view plugin_name, + const std::string_view key, T&& value) +{ + auto* repo_plugins_arr = m_state["repositories"][repo_name]["plugins"].as_array(); + if (!repo_plugins_arr) + die("Couldn't find an array of plugins from repository '{}'", repo_name); + + for (auto&& plugins_node : *repo_plugins_arr) + { + auto* plugin_tbl = plugins_node.as_table(); + if (!plugin_tbl) + continue; + const std::string& name = ManifestSpace::getStrValue(*plugin_tbl, "name"); + if (name != plugin_name) + continue; + + (*plugin_tbl).insert_or_assign(key, std::forward<T>(value)); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", repo_name); + break; + } +} + +#endif diff --git a/cufetchpm/src/main.cpp b/cufetchpm/src/main.cpp new file mode 100644 index 00000000..1bed2731 --- /dev/null +++ b/cufetchpm/src/main.cpp @@ -0,0 +1,440 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <cstdlib> +#include <filesystem> +#include <unordered_map> + +#include "fmt/compile.h" +#include "fmt/os.h" +#include "fmt/ranges.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" +#include "pluginManager.hpp" +#include "stateManager.hpp" +#include "texts.hpp" +#include "util.hpp" + +#if (!__has_include("version.h")) +#error "version.h not found, please generate it with ../scripts/generateVersion.sh" +#else +#include "version.h" +#endif + +#include "getopt_port/getopt.h" + +enum OPs +{ + NONE, + INSTALL, + UPDATE, + LIST, + ENABLE, + DISABLE, + UNINSTALL, + GEN_MANIFEST, + HELP +} op = NONE; + +const std::unordered_map<std::string_view, OPs> map{ + { "install", INSTALL }, { "update", UPDATE }, { "list", LIST }, { "help", HELP }, + { "enable", ENABLE }, { "disable", DISABLE }, { "uninstall", UNINSTALL }, { "gen-manifest", GEN_MANIFEST }, +}; + +OPs str_to_enum(const std::string_view name) +{ + if (auto it = map.find(name); it != map.end()) + return it->second; + return NONE; +} + +void version() +{ + fmt::print( + "cufetchpm {} built from branch '{}' at {} commit '{}' ({}).\n" + "Date: {}\n" + "Tag: {}\n", + VERSION, GIT_BRANCH, GIT_DIRTY, GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE, GIT_COMMIT_DATE, GIT_TAG); + + // if only everyone would not return error when querying the program version :( + std::exit(EXIT_SUCCESS); +} + +void help(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help); + + std::exit(invalid_opt); +} + +void help_install(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help_install); + + std::exit(invalid_opt); +} + +void help_list(int invalid_opt = false) +{ + fmt::print(FMT_COMPILE("{}"), cufetchpm_help_list); + + std::exit(invalid_opt); +} + +bool parse_install_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"force", no_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {"yes", no_argument, nullptr, 'y'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+wfh", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'h': help_install(EXIT_SUCCESS); break; + case '?': help_install(EXIT_FAILURE); break; + + case 'f': options.install_force = true; break; + case 'y': options.install_shut_up = true; break; + } + } + + for (int i = optind; i < argc; ++i) + options.arguments.emplace_back(argv[i]); + + if (options.arguments.empty()) + die("install: no repositories/paths given"); + + return true; +} + +bool parse_list_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"verbose", no_argument, nullptr, 'v'}, + {"help", no_argument, nullptr, 'h'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+vh", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'v': options.list_verbose = true; break; + case 'h': help_list(EXIT_SUCCESS); break; + case '?': help_list(EXIT_FAILURE); break; + } + } + + return true; +} + +bool parse_general_command_args(int argc, char* argv[]) +{ + // clang-format off + const struct option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {0, 0, 0, 0} + }; + // clang-format on + + int opt; + while ((opt = getopt_long(argc, argv, "+h", long_opts, nullptr)) != -1) + { + switch (opt) + { + case 'h': help(EXIT_SUCCESS); break; + case '?': help_install(EXIT_FAILURE); break; + } + } + + for (int i = optind; i < argc; ++i) + options.arguments.emplace_back(argv[i]); + + return true; +} + +static bool parseargs(int argc, char* argv[]) +{ + // clang-format off + int opt = 0; + int option_index = 0; + const char *optstring = "+Vh"; + static const struct option opts[] = { + {"version", no_argument, 0, 'V'}, + {"help", no_argument, 0, 'h'}, + + {0,0,0,0} + }; + + // clang-format on + optind = 1; + while ((opt = getopt_long(argc, argv, optstring, opts, &option_index)) != -1) + { + switch (opt) + { + case 0: break; + case '?': help(EXIT_FAILURE); break; + + case 'V': version(); break; + case 'h': help(); break; + default: return false; + } + } + + if (optind >= argc) + help(EXIT_FAILURE); // no subcommand + + std::string_view cmd = argv[optind]; + int sub_argc = argc - optind - 1; + char** sub_argv = argv + optind + 1; + + op = str_to_enum(cmd); + switch (op) + { + case INSTALL: optind = 0; return parse_install_args(sub_argc, sub_argv); + case LIST: optind = 0; return parse_list_args(sub_argc, sub_argv); + case HELP: break; + default: optind = 0; return parse_general_command_args(sub_argc, sub_argv); + } + + if (op == HELP) + { + if (sub_argc >= 1) + { + std::string_view target = sub_argv[0]; + if (target == "install") + help_install(); + else if (target == "list") + help_list(); + else + die("Couldn't find help text for subcommand '{}'", cmd); + } + else + { + help(EXIT_FAILURE); + } + } + + return true; +} + +void switch_plugin(StateManager&& state, bool switch_) +{ + const char* switch_str = switch_ ? "Enabl" : "Disabl"; // e/ed/ing + const toml::table& tbl = state.get_state(); + + for (const std::string& arg : options.arguments) + { + const size_t pos = arg.find('/'); + if (pos == arg.npos) + die("Plugin to {}e '{}' doesn't have a slash '/' to separate repository and plugin", switch_str, arg); + + const std::string& repo = arg.substr(0, pos); + const std::string& plugin = arg.substr(pos + 1); + + const auto* repo_tbl = tbl["repositories"][repo].as_table(); + if (!repo_tbl) + die("No such repository '{}'", repo); + if (const auto* plugins_arr_tbl = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins_arr_tbl) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl || ManifestSpace::getStrValue(*plugin_tbl, "name") != plugin) + continue; + + for (fs::path base_path : ManifestSpace::getStrArrayValue(*plugin_tbl, "libraries")) + { + if (base_path.extension() == ".disabled") + base_path.replace_extension(); // normalize to enabled form + + const fs::path& enabled_path = base_path; + const fs::path& disabled_path = base_path.string() + ".disabled"; + + fs::path current_path; + if (fs::exists(enabled_path)) + current_path = enabled_path; + else if (fs::exists(disabled_path)) + current_path = disabled_path; + else + { + warn("Plugin library '{}' not found. Skipping", base_path.string()); + continue; + } + + const fs::path& target_path = switch_ ? enabled_path : disabled_path; + if (current_path == target_path) + { + warn("{} is already {}ed", arg, switch_str); + continue; + } + + fs::rename(current_path, target_path); + info("{}ed {}!", switch_str, arg); + } + } + } + } +} + +void list_all_plugins(StateManager&& state) +{ + const auto& is_plugin_disabled = [&](const std::string& manifest_name, const std::string& plugin_name) { + const auto* repo_tbl = state.get_state()["repositories"][manifest_name].as_table(); + if (!repo_tbl) + die("No such repository '{}'", manifest_name); + if (const auto* plugins_arr_tbl = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins_arr_tbl) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl || ManifestSpace::getStrValue(*plugin_tbl, "name") != plugin_name) + continue; + + for (fs::path base_path : ManifestSpace::getStrArrayValue(*plugin_tbl, "libraries")) + if (fs::exists(base_path += ".disabled")) + return true; + } + } + return false; + }; + + if (options.list_verbose) + { + for (const manifest_t& manifest : state.get_all_repos()) + { + fmt::println("\033[1;32mRepository:\033[0m {}", manifest.name); + fmt::println("\033[1;33mURL:\033[0m {}", manifest.url); + fmt::println("\033[1;34mPlugins:"); + for (const plugin_t& plugin : manifest.plugins) + { + fmt::println("\033[1;34m - {}\033[0m", plugin.name); + fmt::println("\t\033[1;35mDescription:\033[0m {}", plugin.description); + fmt::println("\t\033[1;36mAuthor(s):\033[0m {}", fmt::join(plugin.authors, ", ")); + fmt::println("\t\033[1;38;2;255;100;220mDisabled:\033[0m {}", + is_plugin_disabled(manifest.name, plugin.name)); + fmt::println("\t\033[1;38;2;220;220;220mLicense(s):\033[0m {}", fmt::join(plugin.licenses, ", ")); + fmt::println("\t\033[1;38;2;144;238;144mPrefixe(s):\033[0m {}", fmt::join(plugin.prefixes, ", ")); + } + fmt::print("\033[0m"); + } + } + else + { + for (const manifest_t& manifest : state.get_all_repos()) + { + fmt::println("\033[1;32mRepository:\033[0m {} (\033[1;33m{}\033[0m)", manifest.name, manifest.url); + fmt::println("\033[1;34mPlugins:"); + for (const plugin_t& plugin : manifest.plugins) + { + fmt::print(" \033[1;34m{} - \033[1;35m{}", plugin.name, plugin.description); + if (is_plugin_disabled(manifest.name, plugin.name)) + fmt::print(" \033[1;31m(DISABLED)"); + fmt::print("\n"); + } + fmt::print("\033[0m"); + } + } +} + +int main(int argc, char* argv[]) +{ + if (!parseargs(argc, argv)) + return -1; + + fs::create_directories({ getHomeCacheDir() / "cufetchpm" }); + fs::create_directories({ getConfigDir() / "plugins" }); + StateManager state; + switch (op) + { + case INSTALL: + { + if (options.arguments.size() < 1) + die("Please provide a plugin repository to install"); + PluginManager plugin_manager(std::move(state)); + for (const std::string& arg : options.arguments) + { + if (fs::exists(arg)) + plugin_manager.build_plugins(arg); + else + plugin_manager.add_plugins_repo(arg); + } + break; + } + case LIST: + { + list_all_plugins(std::move(state)); + break; + } + case GEN_MANIFEST: + { + if (fs::exists(MANIFEST_NAME) && !askUserYorN(false, "{} already exists. Overwrite it?", MANIFEST_NAME)) + return EXIT_FAILURE; + auto f = fmt::output_file(MANIFEST_NAME, fmt::file::CREATE | fmt::file::WRONLY | fmt::file::TRUNC); + f.print("{}", AUTO_MANIFEST); + f.close(); + break; + } + case ENABLE: + { + switch_plugin(std::move(state), true); + break; + } + case DISABLE: + { + switch_plugin(std::move(state), false); + break; + } + case UPDATE: + { + PluginManager plugin_manager(std::move(state)); + plugin_manager.update_repos(); + break; + } + case UNINSTALL: + { + if (options.arguments.size() < 1) + die("Please provide a plugin repository to uninstall"); + + PluginManager plugin_manager(std::move(state)); + for (const std::string& arg : options.arguments) + plugin_manager.remove_repo(arg); + break; + } + default: warn("uh?"); + } + + return EXIT_SUCCESS; +} diff --git a/cufetchpm/src/manifest.cpp b/cufetchpm/src/manifest.cpp new file mode 100644 index 00000000..d1134fd2 --- /dev/null +++ b/cufetchpm/src/manifest.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "manifest.hpp" + +#include <algorithm> +#include <cctype> +#include <filesystem> +#include <vector> + +#include "libcufetch/common.hh" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +namespace fs = std::filesystem; + +static bool is_valid_name(const std::string_view n) +{ + return std::ranges::all_of(n, + [](const unsigned char c) { return (isalnum(c) || c == '-' || c == '_' || c == '='); }); +} + +std::string ManifestSpace::getStrValue(const toml::table& tbl, const std::string_view name, const std::string_view key) +{ + const std::optional<std::string>& ret = tbl[name][key].value<std::string>(); + return ret.value_or(UNKNOWN); +} + +std::string ManifestSpace::getStrValue(const toml::table& tbl, const std::string_view path) +{ + const std::optional<std::string>& ret = tbl.at_path(path).value<std::string>(); + return ret.value_or(UNKNOWN); +} + +std::vector<std::string> ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view path) +{ + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl.at_path(path).as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + return {}; +} + +std::vector<std::string> ManifestSpace::getStrArrayValue(const toml::table& tbl, const std::string_view name, + const std::string_view value) +{ + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl[name][value].as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + return {}; +} + +CManifest::CManifest(const fs::path& path) +{ + try + { + this->m_tbl = toml::parse_file(path.string()); + } + catch (const toml::parse_error& err) + { + die(_("Failed to parse manifest file at '{}':\n" + "{}\n" + "\t(error occurred at line {} column {})"), + path.string(), err.description(), err.source().begin.line, err.source().begin.column); + } + + parse_manifest(path); +} + +void CManifest::parse_manifest(const fs::path& path) +{ + m_repo.name = getStrValue("repository", "name"); + m_repo.url = getStrValue("repository", "url"); + if (m_repo.name == UNKNOWN) + die("Couldn't find manifest repository name"); + if (!is_valid_name(m_repo.name)) + die("Manifest repository name '{}' is invalid. Only alphanumeric and '-', '_', '=' are allowed in the name", + m_repo.name); + + TinyProcessLib::Process proc(fmt::format("git -C {} rev-parse HEAD", path.parent_path().string()), "", + [&](const char* buf, size_t len) { m_repo.git_hash.assign(buf, len); }); + if (proc.get_exit_status() != 0) + die("manifest: Failed to get repository hash"); + m_repo.git_hash.erase(std::remove(m_repo.git_hash.begin(), m_repo.git_hash.end(), '\n'), m_repo.git_hash.end()); + + if (auto* deps = m_tbl["dependencies"].as_table()) + { + // Collect "all" dependencies + if (auto arr = (*deps)["all"].as_array()) + { + for (auto&& pkg : *arr) + if (auto s = pkg.value<std::string>()) + m_repo.dependencies.push_back(*s); + } + + // Collect platform-specific dependencies + if (auto arr = (*deps)[PLATFORM].as_array()) + { + for (auto&& pkg : *arr) + if (auto s = pkg.value<std::string>()) + m_repo.dependencies.push_back(*s); + } + } + + for (const auto& [name, _] : m_tbl) + { + if (name.str() == "repository" || name.str() == "dependencies") + continue; + + if (!is_valid_name(name.str())) + { + warn("Plugin '{}' has an invalid name. Only alphanumeric and '-', '_', '=' are allowed in the name", + name.str()); + continue; + } + + m_repo.plugins.push_back(get_plugin(name)); + } +} + +plugin_t CManifest::get_plugin(const std::string_view name) +{ + // if (!m_tbl[name].is_table()) + // die("Couldn't find such plugin '{}' in manifest", name); + + return { .name = name.data(), + .description = getStrValue(name, "description"), + .output_dir = getStrValue(name, "output-dir"), + .licenses = getStrArrayValue(name, "licenses"), + .authors = getStrArrayValue(name, "authors"), + .build_steps = getStrArrayValue(name, "build-steps"), + .prefixes = getStrArrayValue(name, "prefixes"), + .platforms = getStrArrayValue(name, "platforms") }; +} diff --git a/cufetchpm/src/pluginManager.cpp b/cufetchpm/src/pluginManager.cpp new file mode 100644 index 00000000..0aea603a --- /dev/null +++ b/cufetchpm/src/pluginManager.cpp @@ -0,0 +1,328 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "pluginManager.hpp" + +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <filesystem> +#include <random> +#include <string> +#include <string_view> +#include <system_error> +#include <vector> + +#include "fmt/format.h" +#include "fmt/ranges.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +static const std::vector<std::string> core_dependencies = { "git" }; // expand in the future, maybe + +using namespace TinyProcessLib; + +static bool has_deps(const std::vector<std::string>& dependencies) +{ + for (const std::string& bin : dependencies) + { + Process proc( + fmt::format("command -v {}", bin), "", [](const char*, size_t) {}, // discard stdout + [](const char*, size_t) {}); // discard stderr + if (proc.get_exit_status() != 0) + return false; + } + + return true; +} + +static bool find_plugin_prefix(const plugin_t& plugin, const plugin_t& pending_plugin) +{ + for (const std::string& prefix : pending_plugin.prefixes) + if (std::find(plugin.prefixes.begin(), plugin.prefixes.end(), prefix) != plugin.prefixes.end()) + return true; + return false; +} + +static bool is_update = false; + +void PluginManager::add_plugins_repo(const std::string& repo) +{ + if (!has_deps(core_dependencies)) + die("Some core dependencies are not installed. You'll need to install: {}", fmt::join(core_dependencies, ", ")); + + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution<> dist(0, 999999); + + // create temponary directory + const fs::path& working_dir = m_cache_path / ("plugin_" + std::to_string(dist(gen))); + fs::create_directories(working_dir); + + // and lets clone the repository + status("Cloning repository '{}' at '{}'", repo, working_dir.string()); + if (Process({ "git", "clone", "--recursive", repo, working_dir.string() }).get_exit_status() != 0) + { + fs::remove_all(working_dir); + die("Failed to clone at directory '{}'", working_dir.string()); + } + success("Successfully cloned. Changing current directory to '{}'", working_dir.string()); + build_plugins(working_dir); +} + +bool PluginManager::is_plugin_conflicting(const plugin_t& pending_plugin) +{ + for (const auto& manifest : m_state_manager.get_all_repos()) + for (const auto& plugin : manifest.plugins) + if (find_plugin_prefix(plugin, pending_plugin)) + return true; + return false; +} + +void PluginManager::update_repos() +{ + for (const manifest_t& repo : m_state_manager.get_all_repos()) + { + std::string output; + auto func = [&](const char* buf, size_t len) { output.assign(buf, len); }; + // the user didn't remove the cache directory, right? + if (fs::exists(m_cache_path / repo.name)) + { + debug("Repo '{}' cache path exists", repo.name); + fs::current_path(m_cache_path / repo.name); + if (Process({ "git", "pull", "--rebase" }, "", func, func).get_exit_status() != 0) + die("Failed to 'git pull --rebase' repository {}: {}", repo.name, output); + debug("git output = {}", output); + + std::string remote; + if (Process( + { "git", "rev-parse", "@{u}" }, "", [&](const char* buf, size_t len) { remote.assign(buf, len); }, + func) + .get_exit_status() != 0) + die("Failed to retrieve upstream hash from repository {}: {}", repo.name, output); + + debug("remote = {} && git_hash = {}", remote, repo.git_hash); + // let's avoid any spaces or newlines + if (hasStart(remote, repo.git_hash)) + { + info("{} is already up-to-date.", repo.name); + continue; + } + + status("Updating {}", repo.name); + is_update = true; + build_plugins(m_cache_path / repo.name); + } + // they did, dammit + else + { + debug("Repo '{}' cache path got deleted/not found", repo.name); + if (Process({ "git", "ls-remote", repo.url, "HEAD" }, "", func, func).get_exit_status() != 0) + die("Failed to retrieve latest commit from url {}: {}", repo.url, output); + + debug("git output = {}", output); + // let's avoid any spaces or newlines + if (hasStart(output, repo.git_hash)) + { + info("{} is already up-to-date.", repo.name); + continue; + } + status("Cloning and then updating {}", repo.name); + is_update = true; + add_plugins_repo(repo.url); + } + } +} + +void PluginManager::build_plugins(const fs::path& working_dir) +{ + std::vector<std::string> non_supported_plugins; + + // cd to the working directory and parse its manifest + fs::current_path(working_dir); + CManifest manifest(working_dir / MANIFEST_NAME); + + // though lets check if we have already installed the plugin in the cache + const fs::path& repo_cache_path = (m_cache_path / manifest.get_repo_name()); + if (fs::exists(repo_cache_path) && !is_update) + { + if (!options.install_force) + { + warn("Repository '{}' already exists in '{}'", manifest.get_repo_name(), repo_cache_path.string()); + fs::remove_all(working_dir); + return; + } + } + + if (!options.install_shut_up) + { + warn("{}", + "You should never blindly trust anything in life that you never saw/know about.\n" + " Right now you are installing something that can be a \033[1;31mPOTENTIAL trojan or any " + "malware.\033[0m\n" + " \033[1;36mPlease make sure that you trust every plugin you put to compile and install.\n" + " \033[1;33mYOU ARE THE SOLE RESPONSABLE FOR ANY DAMAGES DONE ON YOUR MACHINE."); + if (!askUserYorN(false, "Do you want to continue installing these plugins?")) + die("Operation cancelled from the user"); + } + + // So we don't have any plugins in the manifest uh + if (manifest.get_all_plugins().empty()) + { + fs::remove_all(working_dir); + die("Looks like there are no plugins to build in repository '{}'", manifest.get_repo_name()); + } + + if (!manifest.get_dependencies().empty()) + { + info("The plugin repository {} requires the following dependencies, check if you have them installed:\n {}", + manifest.get_repo_name(), fmt::join(manifest.get_dependencies(), ", ")); + if (!options.install_shut_up && !askUserYorN(true, "Are these dependencies installed?")) + die("Balling out, re-install the repository again after installing all dependencies."); + } + + // build each plugin from the manifest + // and add the infos to the state.toml + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + bool found_platform = false; + + if (!plugin.platforms.empty() && plugin.platforms.at(0) != "all") + { + for (const std::string& plugin_platform : plugin.platforms) + if (plugin_platform == PLATFORM) + found_platform = true; + + if (!found_platform) + { + warn("Plugin '{}' doesn't support the platform '{}'. Skipping", plugin.name, PLATFORM); + non_supported_plugins.push_back(plugin.name); + continue; + } + } + + if (is_plugin_conflicting(plugin) && !is_update) + { + warn("Plugin '{}' has conflicting prefixes with other plugins.", plugin.name); + warn("Check with 'cufetchpm list' the plugins that have one of the following prefixes: {}", + fmt::join(plugin.prefixes, ", ")); + if (!options.install_shut_up && !askUserYorN(false, "Wanna continue?")) + { + fs::remove_all(working_dir); + die("Balling out"); + } + } + + status("Trying to build plugin '{}'", plugin.name); + // make the shell stop at the first failure + Process process({ "bash", "-c", fmt::format("set -e; {}", fmt::join(plugin.build_steps, " && ")) }, ""); + if (process.get_exit_status() != 0) + { + fs::remove_all(working_dir); + die("Failed to build plugin '{}'", plugin.name); + } + + success("Successfully built '{}' into '{}'", plugin.name, plugin.output_dir); + } + m_state_manager.add_new_repo(manifest); + + // we built all plugins. let's rename the working directory to its actual manifest name, + success("Repository plugins are successfully built!", repo_cache_path.string()); + status("Renaming directory working directory to '{}'", repo_cache_path.string()); + fs::remove_all(repo_cache_path); + fs::create_directories(repo_cache_path); + fs::rename(working_dir, repo_cache_path); + + // and then we move each plugin built library from its output-dir + // and we'll declare all plugins we have moved. + const fs::path& manifest_config_path = (m_config_path / manifest.get_repo_name()); + fs::create_directories(manifest_config_path); + status("Moving each built plugin to '{}'", manifest_config_path.string()); + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + // already told before + if (std::find(non_supported_plugins.begin(), non_supported_plugins.end(), plugin.name) != + non_supported_plugins.end()) + continue; + + // ugh, devs fault. Report this error to them + if (!fs::exists(plugin.output_dir)) + { + error("Plugin '{}' output-dir '{}' doesn't exist", plugin.name, plugin.output_dir); + continue; + } + + toml::array built_libraries; + for (const auto& library : fs::directory_iterator{ plugin.output_dir }) + { + // ~/.config/customfetch/plugins/<manifest-directory>/<plugin-filename> + const fs::path& library_config_path = manifest_config_path / library.path().filename(); + if (fs::exists(library_config_path) && (!options.install_force || !is_update)) + { + if (options.install_shut_up || askUserYorN(false, "Plugin '{}' already exists. Replace it?", library_config_path.string())) + fs::remove_all(library_config_path); + else + continue; + } + + if (library.is_regular_file() || library.is_symlink()) + { + std::error_code er; + fs::rename(fs::canonical(library), library_config_path, er); + if (er) + { + error("Failed to move '{}' to '{}': {}", fs::canonical(library).string(), + library_config_path.string(), er.message()); + continue; + } + built_libraries.push_back(library_config_path.string()); + } + else + { + error("Built library '{}' is not a regular file", library.path().string()); + } + } + m_state_manager.insert_or_assign_at_plugin(manifest.get_repo_name(), plugin.name, "libraries", + std::move(built_libraries)); + } + success("Enjoy the new plugins from {}", manifest.get_repo_name()); +} + +void PluginManager::remove_repo(const std::string& repo_name) +{ + std::error_code ec; + fs::remove_all(m_cache_path / repo_name, ec); + if (ec) + warn("Failed to remove plugin repository cache path '{}'", (m_cache_path / repo_name).string()); + + fs::remove_all(m_config_path / repo_name, ec); + if (ec) + warn("Failed to remove plugin repository config path '{}'", (m_config_path / repo_name).string()); + + m_state_manager.remove_repo(repo_name); + success("Removed plugin repository '{}'", repo_name); +} diff --git a/cufetchpm/src/stateManager.cpp b/cufetchpm/src/stateManager.cpp new file mode 100644 index 00000000..f38b74fa --- /dev/null +++ b/cufetchpm/src/stateManager.cpp @@ -0,0 +1,189 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "stateManager.hpp" + +#include <filesystem> +#include <fstream> +#include <sstream> +#include <string_view> +#include <utility> + +#include "fmt/base.h" +#include "fmt/os.h" +#include "libcufetch/common.hh" +#include "manifest.hpp" + +using namespace ManifestSpace; + +// https://github.com/hyprwm/Hyprland/blob/2d2a5bebff72c73cd27db3b9e954b8fa2a7623e8/hyprpm/src/core/DataState.cpp#L24 +bool writeState(const std::string& str, const std::string& to) +{ + // create temp file in a safe temp root + const fs::path& temp_state = (fs::temp_directory_path() / ".temp-state"); + std::ofstream of(temp_state, std::ios::trunc); + if (!of.good()) + return false; + + of << str; + of.close(); + + return fs::copy_file(temp_state, to, fs::copy_options::overwrite_existing); +} + +// Ensures a sub-table exists for a given key. Returns a reference to the sub-table. +static toml::table& ensure_table(toml::table& parent, std::string_view key) +{ + if (toml::node* node = parent[key].node()) + if (auto* tbl = node->as_table()) + return *tbl; + + auto [it, inserted] = parent.insert(key, toml::table{}); + return *it->second.as_table(); +} + +// Converts a vector of strings to a toml::array +static toml::array vector_to_array(const std::vector<std::string>& vec) +{ + toml::array ret; + for (const std::string& str : vec) + ret.push_back(str); + return ret; +} + +StateManager::StateManager() +{ + if (!fs::exists(m_path)) + { + auto f = fmt::output_file(m_path.string(), fmt::file::WRONLY | fmt::file::TRUNC | fmt::file::CREATE); + f.print(R"(# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE. +# YOU GONNA MESS SHIT UP. unless you know what you doing ofc + )"); + f.close(); + } + + try + { + if (m_state.empty()) + m_state = toml::parse_file(m_path.string()); + } + catch (const toml::parse_error& err) + { + die(_("Failed to parse state file at '{}':\n" + "{}\n" + "\t(error occurred at line {} column {})"), + m_path.string(), err.description(), err.source().begin.line, err.source().begin.column); + } +} + +void StateManager::add_new_repo(const CManifest& manifest) +{ + toml::table& repositories = ensure_table(m_state, "repositories"); + toml::table& repo = ensure_table(repositories, manifest.get_repo_name()); + repo.insert_or_assign("url", manifest.get_repo_url()); + repo.insert_or_assign("git-hash", manifest.get_repo_hash()); + + toml::array plugins_arr; + for (const plugin_t& plugin : manifest.get_all_plugins()) + { + // will be inserted in alphabetical order + toml::table entry{ { "name", plugin.name }, + { "description", plugin.description }, + { "authors", vector_to_array(plugin.authors) }, + { "licenses", vector_to_array(plugin.licenses) }, + { "prefixes", vector_to_array(plugin.prefixes) } }; + + plugins_arr.push_back(std::move(entry)); + } + + repo.insert_or_assign("plugins", std::move(plugins_arr)); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", manifest.get_repo_name()); +} + +std::vector<manifest_t> StateManager::get_all_repos() +{ + const toml::table* repositories = m_state["repositories"].as_table(); + if (!repositories) + return {}; + + std::vector<manifest_t> manifests; + for (const auto& [repo_name, repo_node] : *repositories) + { + const toml::table* repo_tbl = repo_node.as_table(); + if (!repo_tbl) + continue; + + manifest_t manifest; + manifest.name = repo_name.str(); + manifest.url = getStrValue(*repo_tbl, "url"); + manifest.git_hash = getStrValue(*repo_tbl, "git-hash"); + + if (const toml::array* plugins = repo_tbl->get_as<toml::array>("plugins")) + { + for (const auto& plugin_node : *plugins) + { + const toml::table* plugin_tbl = plugin_node.as_table(); + if (!plugin_tbl) + continue; + + plugin_t plugin; + plugin.name = getStrValue(*plugin_tbl, "name"); + plugin.description = getStrValue(*plugin_tbl, "description"); + plugin.output_dir = getStrValue(*plugin_tbl, "output-dir"); + plugin.authors = getStrArrayValue(*plugin_tbl, "authors"); + plugin.licenses = getStrArrayValue(*plugin_tbl, "licenses"); + plugin.prefixes = getStrArrayValue(*plugin_tbl, "prefixes"); + + manifest.plugins.push_back(std::move(plugin)); + } + } + + manifests.push_back(std::move(manifest)); + } + + return manifests; +} + +void StateManager::remove_repo(const std::string& repo) +{ + toml::table* repo_tbl = m_state["repositories"].as_table(); + if (!repo_tbl->contains(repo)) + return; + + repo_tbl->erase(repo); + std::stringstream ss; + ss << "# AUTO-GENERATED FILE. DO NOT EDIT THIS FILE.\n"; + ss << "# YOU GONNA MESS SHIT UP. unless you know what you doing ofc\n"; + ss << m_state; + + if (!writeState(ss.str(), m_path)) + die("Failed to write plugin state of repository '{}'", repo); +} diff --git a/customfetch.desktop b/customfetch.desktop index 53468545..ba5b3292 100755 --- a/customfetch.desktop +++ b/customfetch.desktop @@ -1,9 +1,9 @@ [Desktop Entry] Name=Customfetch -Comment=Highly customizable and fast fetch program +Comment=Highly customizable and fast neofetch-like program Type=Application Exec=customfetch-gui Terminal=false Categories=Viewer;GTK; -Icon=lookswitcher +Icon=customfetch/Thumbnail.png Keywords=customfetch;cufetch diff --git a/debian/changelog b/debian/changelog index e7721f34..178233f1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,42 @@ +customfetch (2.0.0-beta1) unstable; urgency=medium + + * project: merge 'plugins' branch (#4) - Plugin-based Modules Loader + * project: add macos support (some things WIP) + * misc: create a temponary file when unable to get distro path + * misc: add fastfetch license + update copyright license year + * misc: steal version system from Hyprland (I kinda contributed to that tho) + * config: make offset accept % (super unstable might remove it) + * android widget: trying to refactor c++ code to pure kotlin + * gui: add light colors in pango + * gui: fix crash with empty string and checking for $ at the start, also try to escape more & + * config: deprecate "removable" in favor of "external" in auto.disk.display-types + * config: fix overwrite from arguments + * query: system: fix uptime on android + * args: add --debug + * misc: use compile-time `GUI_APP` instead of runtime config.gui + * android app: move it to https://github.com/Toni500github/customfetch-android-app + * project: add official logos/icons + * tests: add these (stolen from TabAUR) + * workflow: add cmake.yml + cmake: fix build and add install target + * build: add icon prefix + improve uninstall targets + * gui: add icon logo + * args: add --disallow-command-tag + * doc: refactor --help and --how-it-works + * display: don't open the same source-path twice + * assets: fix some ascii art that have single backslash escape + * gui: add gtk css support + * workaround a conflict between the Radeon RX 5500 XT and the (Oland PRO?) Radeon R7 340 + * args: exit --loop-ms with 'q' and increase from 50 to 200 ms + * makefile: force link with fmt static library + * misc: remove `VENDOR_TEST` and `DEVICE_TEST` + * docs: fix some -h incorrections + * misc: fix some things + * misc: ditch selfmade command execute functions in favour of cross-platform tiny-proccess-library + * args: migrate GNU getopt to `getopt_port` + * misc: fix crash with really big `all_ids` string in pci.ids.hpp + + -- toni500 <tonino512@linuxmail.org> Sat, 06 Sep 2025 22:03:03 +0100 + customfetch (1.0.0-1) unstable; urgency=medium * project: add stable support for android. diff --git a/debian/control b/debian/control index 6026c83a..c4c8cbf1 100644 --- a/debian/control +++ b/debian/control @@ -1,14 +1,14 @@ Source: customfetch Section: utils Priority: optional -Maintainer: toni500 <toni500minecraft@gmail.com> +Maintainer: toni500 <tonino512@linuxmail.org> Build-Depends: pkg-config,libgtkmm-3.0-dev,libwayland-dev,libdconf-dev,libglib2.0-dev -Suggests: libwayland-client0,libdconf1,libglib2.0-0 Standards-Version: 4.5.1 Homepage: https://github.com/Toni500github/customfetch Package: customfetch Architecture: any -Depends: ${shlibs:Depends}, ${misc:Depends} -Description: Highly customizable and fast system information fetch program - Inspired by neofetch and fastfetch +Depends: ${shlibs:Depends}, ${misc:Depends}, libgtkmm-3.0-1v5, libgtk-3-0 +Suggests: libwayland-client0,libdconf1,libglib2.0-0 +Description: A modular information fetching (neofetch-like) tool. + Which its focus point is the performance and customizability. diff --git a/debian/copyright b/debian/copyright index 054beeab..90bf099c 100644 --- a/debian/copyright +++ b/debian/copyright @@ -3,13 +3,22 @@ Upstream-Name: customfetch Source: https://github.com/Toni500github/customfetch Files: * -Copyright: 2024 Toni500 +Copyright: 2025, Toni500 License: BSD-3-Clause -Files: include/rapidxml-1.13/* +Files: src/core-modules/**/* +Copyright: 2021 - 2023, Linus Dierheimer +Copyright: 2022 - 2025, Carter Li +License: MIT + +Files: include/libs/rapidxml-1.13/* Copyright: 2006 - 2009, Marcin Kalicinski License: MIT -Files: src/fmt/os.cc src/fmt/format.cc include/fmt/* +Files: include/libs/toml++/toml.hpp +Copyright: Mark Gillard +License: MIT + +Files: src/libs/fmt/os.cc src/libs/fmt/format.cc include/libs/fmt/* Copyright: 2012 - present, Victor Zverovich License: MIT diff --git a/debian/rules b/debian/rules index 3c513aad..cf89406f 100755 --- a/debian/rules +++ b/debian/rules @@ -19,9 +19,15 @@ export DH_VERBOSE = 1 override_dh_auto_build: dh_auto_build -- all DEBUG=0 GUI_APP=0 + cp -f build/release/customfetch customfetch-terminal + + # Clean and build GUI version + $(MAKE) clean + dh_auto_build -- all DEBUG=0 GUI_APP=1 override_dh_auto_install: - dh_auto_install -- all DEBUG=0 GUI_APP=0 + dh_auto_install -- all DEBUG=0 GUI_APP=1 + install -Dm755 customfetch-terminal debian/customfetch/usr/bin/customfetch # dh_make generated override targets # This is example for Cmake (See https://bugs.debian.org/641051 ) diff --git a/examples/mod-library.cc b/examples/mod-library.cc new file mode 100644 index 00000000..916ada0a --- /dev/null +++ b/examples/mod-library.cc @@ -0,0 +1,66 @@ +/* This is an example for a plugin you could install in ~/.config/customfetch/plugins/ + +Plugins are essentially custom(fetch) libraries where you can create modules that you can implement yourself and register using libcufetch! +They have to be compiled as shared libraries **with no name mangling!!**, with one start() and finish() functions. Scroll down for more details on both functions. + +To compile this, just run `g++ -I../include -shared -fPIC mod-library.cc -o mod-library.so`. +*/ + +#include <stdio.h> +#include <cstdlib> +#include <string> + +#include <libcufetch/common.hh> +#include <libcufetch/config.hh> +#include <libcufetch/cufetch.hh> + +/* The handler that we'll use for our module, Handlers return `std::string` + * and take as input the `const callbackInfo_t*` struct + */ +std::string test_func(const callbackInfo_t* _) { + return "Hello!"; +} + +const char *useless_malloc; + +/* Start function. + +The start function takes in a handle, This handle is going to be the libcufetch library. +And also take the config class ConfigBase, instance loaded from customfetch. + +It gets called once we have loaded the plugin shared library. + +*/ +APICALL EXPORT PLUGIN_INIT(void *handle, const ConfigBase& config) { + + /* Our goal in this example is to create a `modification.test` module, This will just return "Hello!" and nothing else. */ + /* The way we'll do this is we will create the test module, with no submodules and a handler (that just returns "Hello!"). */ + /* But then we will also create the modification module, which won't do anything other than hold the test module as a submodule. */ + /* We will then register the modification module. We won't register the test module because it is already a submodule and will be implicitly added. */ + + /* Here we create the 'test' submodule. in customfetch there's no practical difference between a parent module and a submodule. So we just define it like any other module. */ + /* We will not register this, it will be implicitly added through its parent module (so we can't directly invoke `test`, we can only invoke `modification.test`) */ + module_t test_module = {"test", "a generic submodule description", {}, test_func}; + + /* And here we create the 'modification' module. This is what we're actually going to register and it will include the test module as a submodule. */ + /* This module doesn't have a handler, so it can't be used in the config (`modification` won't work). We'll instead use `modification.test` in the config (which does have a handler). */ + module_t modification_module = { "modification", "root module description", { std::move(test_module) }, NULL }; + + /* Register the module. */ + /* This will take the modification module, recursively add it and its submodules to the list, and continue until its finished everything. */ + cfRegisterModule(modification_module); + + /* Lookup the finish function on why this */ + useless_malloc = reinterpret_cast<const char*>(malloc(16)); + + /* And done, after this customfetch will call our handler whenever our test module is invoked in the layout. */ +} + +/* Finish function. + * + * The finish function gets called when customfetch exists. + * You should put free here any thing you have manually allocated to avoid memory leaks. + */ +APICALL EXPORT PLUGIN_FINISH() { + std::free((void*)useless_malloc); +} diff --git a/include/config.hpp b/include/config.hpp index 5fb9b354..c21015ba 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -1,102 +1,83 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _CONFIG_HPP #define _CONFIG_HPP +#undef TOML_HEADER_ONLY #define TOML_HEADER_ONLY 0 -#include <cstdint> -#include <type_traits> -#include <unordered_map> - -#include "toml++/toml.hpp" -#include "util.hpp" - -enum types -{ - STR, - BOOL, - INT -}; - -struct override_configs_types -{ - types value_type; - std::string string_value = ""; - bool bool_value = false; - int int_value = 0; -}; - -// config colors -// those without gui_ prefix are for the terminal -struct colors_t -{ - std::string black; - std::string red; - std::string green; - std::string blue; - std::string cyan; - std::string yellow; - std::string magenta; - std::string white; - - std::string gui_black; - std::string gui_red; - std::string gui_green; - std::string gui_blue; - std::string gui_cyan; - std::string gui_yellow; - std::string gui_magenta; - std::string gui_white; -}; +#include <filesystem> +#include "libcufetch/config.hh" -class Config +class Config : public ConfigBase { public: // Create .config directories and files and load the config file (args or default) - Config(const std::string_view configFile, const std::string_view configDir); + Config(const std::filesystem::path& configFile, const std::filesystem::path& configDir); + + // config colors + // those without gui_ prefix are for the terminal + struct colors_t + { + std::string black; + std::string red; + std::string green; + std::string blue; + std::string cyan; + std::string yellow; + std::string magenta; + std::string white; + + std::string gui_black; + std::string gui_red; + std::string gui_green; + std::string gui_blue; + std::string gui_cyan; + std::string gui_yellow; + std::string gui_magenta; + std::string gui_white; + } colors; // Variables of config file in [config] table std::vector<std::string> layout; std::vector<std::string> percentage_colors; std::vector<std::string> colors_name, colors_value; std::string source_path; - std::string font; std::string data_dir; std::string sep_reset; std::string title_sep; std::string gui_bg_image; + std::string gui_css_file; std::string ascii_logo_type; std::string logo_position; - std::uint16_t offset = 0; + std::string offset; std::uint16_t logo_padding_left = 0; std::uint16_t logo_padding_top = 0; std::uint16_t layout_padding_top = 0; std::uint32_t loop_ms = 0; - bool gui = false; bool sep_reset_after = false; bool slow_query_warnings = false; bool use_SI_unit = false; @@ -106,7 +87,7 @@ class Config // modules specific configs // [auto.disk] std::string auto_disks_fmt; - int auto_disks_types = 0; + int auto_disks_types = 0; bool auto_disks_show_dupl = false; // [os.uptime] @@ -123,15 +104,15 @@ class Config std::vector<std::string> apk_files; // inner management / argument configs - std::vector<std::string> m_args_layout; - std::string m_custom_distro; - std::string m_image_backend; - bool m_disable_source = false; - bool m_disable_colors = false; - bool m_display_distro = true; - bool m_print_logo_only = false; - - std::unordered_map<std::string, override_configs_types> overrides; + std::vector<std::string> args_layout; + std::string args_custom_distro; + std::string args_image_backend; + std::uint16_t m_offset_calc = 0; + bool m_display_distro = true; + bool args_disable_source = false; + bool args_disable_colors = false; + bool args_disallow_commands = false; + bool args_print_logo_only = false; /** * Load config file and parse every config variables @@ -139,13 +120,13 @@ class Config * @param colors The colors struct where we'll put the default config colors. * It doesn't include the colors in config.alias-colors */ - void loadConfigFile(const std::string_view filename, colors_t& colors); + void loadConfigFile(const std::filesystem::path& filename); /** - * Generate a config file + * Generate the default config file at path * @param filename The config file path */ - void generateConfig(const std::string_view filename); + void generateConfig(const std::filesystem::path& filename); /** * Add alias values to colors_name and colors_value. @@ -157,287 +138,40 @@ class Config /** * Override a config value from --override - * @param str The value to override. + * @param opt The value to override. * Must have a '=' for separating the name and value to override. * NO spaces between - */ + */ void overrideOption(const std::string& opt); -private: - // Parsed config from loadConfigFile() - toml::table tbl; - /** - * Get value of config variables - * @param value The config variable "path" (e.g "config.source-path") - * @param fallback Default value if couldn't retrive value + * Override a config value from --override + * @param key The value name to override. + * Must have a '=' for separating the name and value to override. + * NO spaces between + * @param value The value that will overwrite */ template <typename T> - T getValue(const std::string_view value, const T&& fallback, bool dont_expand_var = false) const + void overrideOption(const std::string& key, const T& value) { - const auto& overridePos = overrides.find(value.data()); - - // user wants a bool (overridable), we found an override matching the name, and the override is a bool. - if constexpr (std::is_same<T, bool>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == BOOL) - return overrides.at(value.data()).bool_value; - - // user wants a str (overridable), we found an override matching the name, and the override is a str. - if constexpr (std::is_same<T, std::string>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR) - return overrides.at(value.data()).string_value; - - if constexpr (std::is_same<T, std::uint16_t>()) - if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT) - return overrides.at(value.data()).int_value; - - const std::optional<T> ret = this->tbl.at_path(value).value<T>(); - if constexpr (toml::is_string<T>) // if we want to get a value that's a string - return ret ? expandVar(ret.value(), dont_expand_var) : expandVar(fallback, dont_expand_var); - else - return ret.value_or(fallback); + override_configs_types o; + if constexpr (std::is_same_v<T, bool>) + { + o.value_type = BOOL; + o.bool_value = value; + } + else if constexpr (std::is_convertible_v<T, std::string>) + { + o.value_type = STR; + o.string_value = value; + } + else if constexpr (std::is_convertible_v<T, int>) + { + o.value_type = INT; + o.int_value = value; + } + overrides[key] = std::move(o); } - - /** - * getValue() but don't want to specify the template, so it's std::string, - * and because of the name, only used when retriving the colors for terminal and GUI - * @param value The config variable "path" (e.g "config.gui-red") - * @param fallback Default value if couldn't retrive value - */ - std::string getThemeValue(const std::string_view value, const std::string_view fallback) const; - - /** - * Get value of config array of string variables - * @param value The config variable "path" (e.g "config.gui-red") - * @param fallback Default value if couldn't retrive value - */ - std::vector<std::string> getValueArrayStr(const std::string_view value, const std::vector<std::string>& fallback); }; -// default config -inline constexpr std::string_view AUTOCONFIG = R"#([config] - -# For more information on how customfetch works and the layout, -# Read either: -# * -w or --how-it-works -# * the manual customfetch.1 -# * if on the android app, click the button "how it works" during widget configuration -layout = [ - "$<title>", - "$<title_sep>", - "${auto}OS: $<os.name> $<system.arch>", - "${auto}Host: $<system.host>", - "${auto}Kernel: $<os.kernel>", - "${auto}Uptime: $<os.uptime>",)#" -#if !ANDROID_APP - R"#( - "${auto}Terminal: $<user.terminal>", - "${auto}Shell: $<user.shell>", - "${auto}Packages: $<os.pkgs>",)#" -#endif -#if !CF_ANDROID - R"#( - "${auto}Theme: $<theme-gtk-all.name>", - "${auto}Icons: $<theme-gtk-all.icons>", - "${auto}Font: $<theme-gtk-all.font>", - "${auto}Cursor: $<theme.cursor>", - "${auto}WM: $<user.wm_name>", - "${auto}DE: $<user.de_name>",)#" -#endif - R"#( - "$<auto.disk>", - "${auto}Swap: $<swap>", - "${auto}CPU: $<cpu>", - "${auto}GPU: $<gpu>", - "${auto}RAM: $<ram>", - "", - "$<colors>", # normal colors - "$<colors_light>" # light colors -] - -# display ascii-art or image/gif (GUI only) near layout -# put "os" for displaying the OS ascii-art -# or the "/path/to/file" for displaying custom files -# or "off" for disabling ascii-art or image displaying -source-path = "os" - -# Path to where we'll take all the distros/OSs ascii arts. -# note: it MUST contain an "ascii" subdirectory -)#" -#if !ANDROID_APP -R"#(data-dir = "/usr/share/customfetch")#" -#else -R"#(data-dir = "/data/user/0/org.toni.customfetch_android/files")#" -#endif -R"#( - -# The type of ASCII art to apply ("small", "old"). -# Basically will add "_<type>" to the logo filename. -# It will return the regular linux ascii art if it doesn't exist. -# Leave empty it for regular. -)#" -#if !ANDROID_APP -R"#(ascii-logo-type = "")#" -#else -R"#(ascii-logo-type = "small")#" -#endif -R"#( - -# A char (or string) to use in $<title_sep> -title-sep = "-" - -# A separator (or string) that when encountered, will automatically -# reset color, aka. automatically add ${0} (only in layout) -# Make it empty for disabling -sep-reset = ":" - -# Should we reset color after or before the separator? -# true = after ("test ->${0} ") -# false = before ("test ${0}-> ") -sep-reset-after = false - -# Where the logo should be displayed. -# Values: "top" or "left" or "bottom" -logo-position = "left" - -# Offset between the ascii art and the layout -offset = 5 - -# Padding between the start and the ascii art -logo-padding-left = 0 - -# Padding of the ascii art from the top -logo-padding-top = 0 - -# Padding of the layout from the top -layout-padding-top = 0 - -# Usually in neofetch/fastfetch, when your terminal size is too small, -# to render some text in 1 line, they don't wrap those lines, instead they truncate them. -# Enable/Disable if you want this -)#" -#if !ANDROID_APP -R"#(wrap-lines = false)#" -#else -R"#(wrap-lines = true)#" -#endif -R"#( - -# Used in disk, ram and swap modules. -# If true, we're going to use the SI standard byte unit (1kB == 1000 bytes) -# Else if false, we using the IEC byte unit (1KiB == 1024 bibytes) -# Really nerdy stuff -use-SI-byte-unit = false - -# Warn against tradeoffs between slower queries for availability -# e.g. falling back to gsettings when we can't find the config file for GTK -slow-query-warnings = false - -# Colors in the terminal -black = "\e[1;30m" -red = "\e[1;31m" -green = "\e[1;32m" -yellow = "\e[1;33m" -blue = "\e[1;34m" -magenta = "\e[1;35m" -cyan = "\e[1;36m" -white = "\e[1;37m" - -# Alias colors. Basically more color variables. -# They can be used as like as the color tag. -# This is as like as using the --add-color argument -# Syntax must be "name=value", e.g "purple=magenta" or "orange=!#F08000" -alias-colors = ["purple=magenta"] - -# Colors to be used in percentage tag and modules members. -# They are used as if you're using the color tag. -# It's an array just for "convenience" -# 1st color for good -# 2nd color for normal -# 3rd color for bad -percentage-colors = ["green", "yellow", "red"] - -# $<auto.disk> config -[auto.disk] -# Format for displaying the auto detected disks infos -# %1 = mount directory -# %2 = device path -# %3 = type of filesystem -# %4 = total amount of storage -# %5 = free amount of storage -# %6 = used amount of storage -# %7 = percentage of used storage -# %8 = percentage of free storage -fmt = "${auto}Disk (%1): $<disk(%1)>" - -# Only print disks that matches the description -# of the following types: -# regular = Regular disks (internel M.2 SSD, ...) (won't be specified) -# removable = External disks (USB, SATA, ...) -# read-only = Disks with read-only filesystems -# hidden = Disks that are not really mounted by the user -display-types = ["regular", "removable", "read-only"] - -# In some OSes such as NixOS or Android, there might be some directories that are bind mounted. -# Bind mounted directories create an additional view of an existing directory, -# and `statfs()` on the mount point will return the filesystem statistics of the original directory. -show-duplicated = false - -# $<os.uptime> config -[os.uptime] -# how to display the name of the uptime -# e.g: hours = "hrs" -> "Uptime: 3hrs" -days = " days" -hours = " hours" -mins = " mins" -secs = " seconds" - -# $<os.pkgs> config -[os.pkgs] -# Ordered list of which packages installed count should be displayed in $<os.pkgs> -# remember to not enter the same name twice, else the world will finish -# Choices: pacman, flatpak, dpkg, apk -# -# Pro-tip: if your package manager isn't listed here, yet, -# use the bash command tag in the layout -# e.g "Packages: $(pacman -Q | wc -l) (pacman)" -pkg-managers = ["pacman", "dpkg", "flatpak"] - -# Distros and package manager specific -# package manager paths for getting the packages count from path. -# They are arrays so you can add multiple paths. -# -# If you don't know what these ares, leave them by default settings -pacman-dirs = ["/var/lib/pacman/local/"] -dpkg-files = ["/var/lib/dpkg/status", "/data/data/com.termux/files/usr/var/lib/dpkg/status"] -flatpak-dirs = ["/var/lib/flatpak/app/", "~/.local/share/flatpak/app/"] -apk-files = ["/var/lib/apk/db/installed"] - -# GUI app options -[gui] - -# Font to be used -# syntax must be [FAMILY-LIST] [STYLE-OPTIONS] [SIZE] -# e.g "Liberation Mono Normal 12" -# check https://lazka.github.io/pgi-docs/Pango-1.0/classes/FontDescription.html#Pango.FontDescription for more infos -font = "Liberation Mono Normal 12" - -# These are the colors you can use in the GUI mode. -# They overwrite the terminal colors from above. -# They can only have hexcodes colors -black = "!#000005" -red = "!#ff2000" -green = "!#00ff00" -blue = "!#00aaff" -cyan = "!#00ffff" -yellow = "!#ffff00" -magenta = "!#f881ff" -white = "!#ffffff" - -# Path to image as a background. -# put "disable" for disabling and use the theme color as background. -bg-image = "disable" - -)#"; - #endif // _CONFIG_HPP diff --git a/include/core-modules.hh b/include/core-modules.hh new file mode 100644 index 00000000..18951027 --- /dev/null +++ b/include/core-modules.hh @@ -0,0 +1,144 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <pwd.h> +#include <sys/utsname.h> + +#include "config.hpp" +#include "libcufetch/cufetch.hh" + +#define MODFUNC(name) std::string name(__attribute__((unused)) const callbackInfo_t* callbackInfo) + +// system.cc +MODFUNC(arch); +MODFUNC(host); +MODFUNC(host_name); +MODFUNC(host_version); +MODFUNC(host_vendor); + +// os.cc +inline utsname g_uname_infos; +inline std::FILE* os_release; +MODFUNC(os_name); +MODFUNC(os_pretty_name); +MODFUNC(os_name_id); +MODFUNC(os_version_id); +MODFUNC(os_version_codename); +unsigned long os_uptime(); +MODFUNC(os_kernel_name); +MODFUNC(os_kernel_version); +MODFUNC(os_hostname); +MODFUNC(os_initsys_name); +MODFUNC(os_initsys_version); + +// cpu.cc +inline std::FILE* cpuinfo; +MODFUNC(cpu_freq_cur); +MODFUNC(cpu_freq_max); +MODFUNC(cpu_freq_min); +MODFUNC(cpu_freq_bios); +float cpu_temp(); +MODFUNC(cpu_nproc); +MODFUNC(cpu_name); +MODFUNC(android_cpu_vendor); +MODFUNC(android_cpu_model_name); + +// user.cc +inline struct passwd* g_pwd; +inline bool is_tty = false; +inline std::string term_pid, term_name, wm_name, de_name, wm_path_exec; +std::string get_terminal_name(); +std::string get_terminal_pid(); +MODFUNC(user_name); +MODFUNC(user_shell_path); +MODFUNC(user_shell_name); +MODFUNC(user_shell_version); +MODFUNC(user_term_name); +MODFUNC(user_term_version); +MODFUNC(user_wm_name); +MODFUNC(user_wm_version); +MODFUNC(user_de_name); +MODFUNC(user_de_version); + +// ram.cc and swap.cc +inline std::FILE* meminfo; +double ram_free(); +double ram_total(); +double ram_used(); +double swap_free(); +double swap_total(); +double swap_used(); + +// disk.cc +enum +{ + DISK_VOLUME_TYPE_HIDDEN = 1 << 2, + DISK_VOLUME_TYPE_REGULAR = 1 << 3, + DISK_VOLUME_TYPE_EXTERNAL = 1 << 4, + DISK_VOLUME_TYPE_READ_ONLY = 1 << 5, +}; + +inline std::FILE* mountsFile; +MODFUNC(disk_fsname); +MODFUNC(disk_device); +MODFUNC(disk_mountdir); +MODFUNC(disk_types); +MODFUNC(auto_disk); +double disk_total(const callbackInfo_t* callbackInfo); +double disk_free(const callbackInfo_t* callbackInfo); +double disk_used(const callbackInfo_t* callbackInfo); + +// battery.cc +MODFUNC(battery_modelname); +MODFUNC(battery_perc); +MODFUNC(battery_status); +MODFUNC(battery_capacity_level); +MODFUNC(battery_technology); +MODFUNC(battery_vendor); +double battery_temp(); + +// gpu.cc +MODFUNC(gpu_name); +MODFUNC(gpu_vendor); + +// theme.cc +MODFUNC(theme_gtk_name); +MODFUNC(theme_gtk_icon); +MODFUNC(theme_gtk_font); +MODFUNC(theme_cursor_name); +MODFUNC(theme_cursor_size); +MODFUNC(theme_gtk_all_name); +MODFUNC(theme_gtk_all_icon); +MODFUNC(theme_gtk_all_font); +MODFUNC(theme_gsettings_name); +MODFUNC(theme_gsettings_icon); +MODFUNC(theme_gsettings_font); +MODFUNC(theme_gsettings_cursor_name); +MODFUNC(theme_gsettings_cursor_size); + +void core_plugins_start(const Config& config); +void core_plugins_finish(); diff --git a/include/display.hpp b/include/display.hpp index a0f84246..d0169116 100644 --- a/include/display.hpp +++ b/include/display.hpp @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -30,6 +30,54 @@ #include <vector> #include "config.hpp" +#include "libcufetch/cufetch.hh" +#include "platform.hpp" + +#if CF_MACOS +constexpr std::string_view ascii_logo = +R"(${green} c.' +${green} ,xNMM. +${green} .OMMMMo +${green} lMM" +${green} .;loddo:. .olloddol;. +${green} cKMMMMMMMMMMNWMMMMMMMMMM0: +${yellow} .KMMMMMMMMMMMMMMMMMMMMMMMWd. +${yellow} XMMMMMMMMMMMMMMMMMMMMMMMX. +${red};MMMMMMMMMMMMMMMMMMMMMMMM: +${red}:MMMMMMMMMMMMMMMMMMMMMMMM: +${red}.MMMMMMMMMMMMMMMMMMMMMMMMX. +${red} kMMMMMMMMMMMMMMMMMMMMMMMMWd. +${magenta} 'XMMMMMMMMMMMMMMMMMMMMMMMMMMk +${magenta} 'XMMMMMMMMMMMMMMMMMMMMMMMMK. + ${blue}kMMMMMMMMMMMMMMMMMMMMMMd + ${blue};KMMMMMMMWXXWMMMMMMMk. + ${blue}"cooc*" "*coo'" +)"; +#elif CF_ANDROID +constexpr std::string_view ascii_logo = +R"(${green} ;, ,; +${green} ';,.-----.,;' +${green} ,' ', +${green} / O O \\ +${green}| | +${green}'-----------------' +)"; +#else +constexpr std::string_view ascii_logo = +R"(${black} ##### +${black} ####### +${black} ##${1}O${black}#${1}O${black}## +${black} #${yellow}#####${black}# +${black} ##${1}##${yellow}###${1}##${black}## +${black} #${1}##########${black}## +${black} #${1}############${black}## +${black} #${1}############${black}### +${yellow} ##${black}#${1}###########${black}##${yellow}# +${yellow}######${black}#${1}#######${black}#${yellow}###### +${yellow}#######${black}#${1}#####${black}#${yellow}####### +${yellow} #####${black}#######${yellow}##### +)"; +#endif namespace Display { @@ -37,12 +85,11 @@ namespace Display /* * Render the layout along side the source file and return the vector * @param config The config class - * @param colors The colors * @param already_analyzed_path If already checked that the source path is not a binary file * @param path Path to source file */ -std::vector<std::string> render(const Config& config, const colors_t& colors, const bool already_analyzed_path, - const std::string_view path); +std::vector<std::string> render(const Config& config, const bool already_analyzed_path, + const std::filesystem::path& path, const moduleMap_t& moduleMap); /* * Display the rendered result (or just display a normal vector of string @@ -56,6 +103,17 @@ void display(const std::vector<std::string>& renderResult); */ std::string detect_distro(const Config& config); +inline unsigned int calc_perc(const float perc, const int width, const int len) +{ + const int ret = (perc / 100 * width) - (perc / 100 * len); + debug("maxLineLength = {}", len); + debug("calc_perc ret = {}", ret); + return ret > 0 ? ret : 0; +} + +// default ascii logo fd +inline int ascii_logo_fd = -1; + } // namespace Display #endif diff --git a/include/gui.hpp b/include/gui.hpp index d7cfce90..0f855720 100644 --- a/include/gui.hpp +++ b/include/gui.hpp @@ -1,32 +1,32 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _GUI_HPP #define _GUI_HPP -#if GUI_APP && !ANDROID_APP +#if GUI_APP #include "config.hpp" #include "gdkmm/pixbuf.h" @@ -38,6 +38,7 @@ #include "gtkmm/label.h" #include "gtkmm/overlay.h" #include "gtkmm/window.h" +#include "libcufetch/cufetch.hh" namespace GUI { @@ -48,18 +49,17 @@ class Window : public Gtk::Window /** * Initialize and create everything and parse layout with source path. * @param config The config class - * @param colors The non-alias colors struct * @param path The logo source path */ - Window(const Config& config, const colors_t& colors, const std::string_view path); + Window(const Config& config, const std::filesystem::path& path, const moduleMap_t& moduleMap); // Destroy the window, handled by GTK virtual ~Window(); private: - const Config& m_config; - const colors_t& m_colors; - const std::string_view m_path; - bool isImage; + const Config& m_config; + const std::filesystem::path& m_path; + const moduleMap_t& m_moduleMap; + bool m_isImage; Gtk::Overlay m_overlay; Gtk::Box m_box; @@ -125,6 +125,6 @@ class Window : public Gtk::Window } // namespace GUI -#endif // GUI_APP && !ANDROID_APP +#endif // GUI_APP #endif // _GUI_HPP diff --git a/include/libcufetch/common.hh b/include/libcufetch/common.hh new file mode 100644 index 00000000..a80949ce --- /dev/null +++ b/include/libcufetch/common.hh @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <cstdlib> + +#include "fmt/core.h" + +constexpr const char NOCOLOR[] = "\033[0m"; +constexpr const char NOCOLOR_BOLD[] = "\033[0m\033[1m"; + +// Didn't find what you were looking for. +constexpr const char UNKNOWN[] = "(unknown)"; + +// Usually in neofetch/fastfetch when some infos couldn't be queried, they remove it from the display. +// With customfetch is kinda difficult to know when to remove the info to display, +// since it's all modular with tags, so I have created a "magic line" to be sure that I don't cut the wrong line. +// +// Every instance of this string found in a layout line, the whole line will be erased. +constexpr const char MAGIC_LINE[] = "(cut this line NOW!! RAHHH)"; + +#define APICALL extern "C" +#define EXPORT __attribute__((visibility("default"))) +#define PLUGIN_INIT void start +#define PLUGIN_FINISH void finish + +#if DEBUG +inline bool debug_print = true; +#else +inline bool debug_print = false; +#endif + +// std::format function arguments +// Print to stderr an error with header 'ERROR:' in red +template <typename... Args> +void error(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;31mERROR: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stderr an error with header 'FATAL:' in red and exit with failure code +template <typename... Args> +void die(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;31mFATAL: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); + std::exit(EXIT_FAILURE); +} + +// std::format function arguments +// Print to stdout a debug msg with header '[DEBUG]' in hot-pink color +// only if debug_print is set (do not modify it). +template <typename... Args> +void debug(const std::string_view fmt, Args&&... args) noexcept +{ + if (debug_print) + fmt::print(stdout, "\033[1;38;2;255;105;180m[DEBUG]:\033[0m {}\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stderr a warning with header 'WARNING:' in yellow +template <typename... Args> +void warn(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stderr, "\033[1;33mWARNING: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} + +// std::format function arguments +// Print to stdout an info msg with header 'INFO:' in cyan +template <typename... Args> +void info(const std::string_view fmt, Args&&... args) noexcept +{ + fmt::print(stdout, "\033[1;36mINFO: {}\033[0m\n", + fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +} diff --git a/include/libcufetch/config.hh b/include/libcufetch/config.hh new file mode 100644 index 00000000..b1743bc4 --- /dev/null +++ b/include/libcufetch/config.hh @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <string> +#include <unordered_map> + +#define TOML_HEADER_ONLY 0 +#include "libcufetch/common.hh" +#include "toml++/toml.hpp" + +enum types +{ + STR, + BOOL, + INT +}; + +struct override_configs_types +{ + types value_type; + std::string string_value = ""; + bool bool_value = false; + int int_value = 0; +}; + +class EXPORT ConfigBase +{ +public: + /** + * Get value of config variables + * @param value The config variable "path" (e.g "config.source-path") + * @param fallback Default value if couldn't retrive value + */ + template <typename T> + T getValue(const std::string_view value, const T& fallback) const + { + const auto& overridePos = overrides.find(value.data()); + + // user wants a bool (overridable), we found an override matching the name, and the override is a bool. + if constexpr (std::is_convertible_v<T, bool>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == BOOL) + return overrides.at(value.data()).bool_value; + + if constexpr (std::is_convertible_v<T, std::string>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == STR) + return overrides.at(value.data()).string_value; + + if constexpr (std::is_convertible_v<T, int>) + if (overridePos != overrides.end() && overrides.at(value.data()).value_type == INT) + return overrides.at(value.data()).int_value; + + const std::optional<T>& ret = this->tbl.at_path(value).value<T>(); + return ret.value_or(fallback); + } + + /** + * Get value of config array of string variables + * @param value The config variable "path" (e.g "config.gui-red") + * @param fallback Default value if couldn't retrive value + */ + std::vector<std::string> getValueArrayStr(const std::string_view value, + const std::vector<std::string>& fallback) const + { + std::vector<std::string> ret; + + // https://stackoverflow.com/a/78266628 + if (const toml::array* array_it = tbl.at_path(value).as_array()) + { + array_it->for_each([&ret](auto&& el) { + if (const toml::value<std::string>* str_elem = el.as_string()) + ret.push_back((*str_elem)->data()); + }); + + return ret; + } + else + { + return fallback; + } + } + +protected: + std::unordered_map<std::string, override_configs_types> overrides; + + // Parsed config from loadConfigFile() + toml::table tbl; +}; diff --git a/include/libcufetch/cufetch.hh b/include/libcufetch/cufetch.hh new file mode 100644 index 00000000..68f19e49 --- /dev/null +++ b/include/libcufetch/cufetch.hh @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <functional> +#include <string> +#include <vector> + +#include "libcufetch/parse.hh" + +/* A linked list including module arguments. An argument may be specified for any part of the module path (e.g. + * `disk(/).used(GiB)`, `test.hi(a)`) */ +struct moduleArgs_t +{ + struct moduleArgs_t* prev = nullptr; + + std::string name; + std::string value; + + struct moduleArgs_t* next = nullptr; +}; + +// Struct used in modules callback functions (handler in module_t) +struct callbackInfo_t +{ + const moduleArgs_t* moduleArgs; + parse_args_t& parse_args; +}; + +/* Main struct for declaring a customfetch module. + * + * Submodules are referenced with '.' in their path. + * Example: $<parent.child> -> parent = `name`, child = `submodules[x].name`. + * + * WARN: Do not pass submodules to cfRegisterModule. + * It registers recursively and will include them automatically. + * + * Real example: $<github.profile.following> + * - github = root module + * - profile = submodule of github + * - following = submodule of profile + * + * The handler is executed when the module is invoked in the layout. + * If it's NULL, it returns "(unknown/invalid module)" + * + * Code example: + * module_t submodule_foo = {"idk", "description", {}, submodule_foo_callback}; + * module_t foo = {"foo", "description", {std::move(submodule_foo)}, foo_callback}; + * cfRegisterModule(foo); // you can call $<foo> and $<foo.idk> from the layout. + */ +struct module_t +{ + std::string name; + std::string description; + std::vector<module_t> submodules; /* Use std::move() for efficiency when adding. */ + std::function<std::string(const callbackInfo_t*)> handler; +}; + +// C ABI is needed to prevent symbol mangling, but we don't actually need C compatibility, +// so we ignore this warning about return types that are potentially incompatible with C. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +/* Register a module, and its submodules, to customfetch. */ +APICALL EXPORT void cfRegisterModule(const module_t& module); + +/* Get a list of all modules registered. */ +APICALL EXPORT const std::vector<module_t>& cfGetModules(); diff --git a/include/fmt/args.h b/include/libcufetch/fmt/args.h similarity index 100% rename from include/fmt/args.h rename to include/libcufetch/fmt/args.h diff --git a/include/fmt/base.h b/include/libcufetch/fmt/base.h similarity index 100% rename from include/fmt/base.h rename to include/libcufetch/fmt/base.h diff --git a/include/fmt/chrono.h b/include/libcufetch/fmt/chrono.h similarity index 100% rename from include/fmt/chrono.h rename to include/libcufetch/fmt/chrono.h diff --git a/include/fmt/color.h b/include/libcufetch/fmt/color.h similarity index 100% rename from include/fmt/color.h rename to include/libcufetch/fmt/color.h diff --git a/include/fmt/compile.h b/include/libcufetch/fmt/compile.h similarity index 100% rename from include/fmt/compile.h rename to include/libcufetch/fmt/compile.h diff --git a/include/fmt/core.h b/include/libcufetch/fmt/core.h similarity index 100% rename from include/fmt/core.h rename to include/libcufetch/fmt/core.h diff --git a/include/fmt/format-inl.h b/include/libcufetch/fmt/format-inl.h similarity index 100% rename from include/fmt/format-inl.h rename to include/libcufetch/fmt/format-inl.h diff --git a/include/fmt/format.h b/include/libcufetch/fmt/format.h similarity index 100% rename from include/fmt/format.h rename to include/libcufetch/fmt/format.h diff --git a/include/fmt/os.h b/include/libcufetch/fmt/os.h similarity index 100% rename from include/fmt/os.h rename to include/libcufetch/fmt/os.h diff --git a/include/fmt/ostream.h b/include/libcufetch/fmt/ostream.h similarity index 100% rename from include/fmt/ostream.h rename to include/libcufetch/fmt/ostream.h diff --git a/include/fmt/ranges.h b/include/libcufetch/fmt/ranges.h similarity index 100% rename from include/fmt/ranges.h rename to include/libcufetch/fmt/ranges.h diff --git a/include/fmt/std.h b/include/libcufetch/fmt/std.h similarity index 100% rename from include/fmt/std.h rename to include/libcufetch/fmt/std.h diff --git a/include/libcufetch/parse.hh b/include/libcufetch/parse.hh new file mode 100644 index 00000000..c71cfc2c --- /dev/null +++ b/include/libcufetch/parse.hh @@ -0,0 +1,99 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#pragma once + +#include <string> +#include <unordered_map> +#include <vector> + +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" + +// C ABI is needed to prevent symbol mangling, but we don't actually need C compatibility, +// so we ignore this warning about return types that are potentially incompatible with C. +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + +struct module_t; + +// Map from a modules name to its pointer. +using moduleMap_t = std::unordered_map<std::string, const module_t&>; + +/* Context struct used when parsing tags in strings. + * @param input The string to parse + * @param modulesInfo The system infos + * @param pureOutput The output of the string but without tags + * @param layout The layout of customfetch + * @param tmp_layout The temponary layout to be used for multiple-line modules + * @param config The config + * @param no_more_reset If we are recursively parsing, e.g we are inside tags + */ +struct EXPORT parse_args_t +{ + const moduleMap_t& modulesInfo; + std::string& pureOutput; + std::vector<std::string>& layout; + std::vector<std::string>& tmp_layout; + const ConfigBase& config; + bool parsingLayout; + bool firstrun_clr = true; + bool no_more_reset = false; +}; + +/* Parse input, in-place, with data from modulesInfo. + * Documentation on formatting is in the flag -w or the customfetch.1 manual. + * @param input The string to parse + * @param modulesInfo The system infos + * @param pureOutput The output of the string but without tags + * @param layout The layout of customfetch + * @param tmp_layout The temponary layout to be used for multiple-line modules + * @param config The config + * @param parsingLayout If we are parsing layout or not + * @param no_more_reset If we are recursively parsing, e.g we are inside tags + */ +std::string parse(std::string input, const moduleMap_t& modulesInfo, std::string& pureOutput, + std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, const ConfigBase& config, + const bool parsingLayout, bool& no_more_reset); + +/* Parse input, in-place, with data from modulesInfo. + * Documentation on formatting is in the flag -w or the customfetch.1 manual. + * @param input The string to parse + * @param parse_args The parse arguments to be used (parse_args_t) + */ +APICALL EXPORT std::string parse(const std::string& input, parse_args_t& parse_args); + +/* + * Create a colored percentage from parse() + * @param n1 The first number + * @param n2 The second number + * @param parse_args The parse() parameters + * @param invert Is the result high number bad or good? + * @return The colored percentage with ending % + */ +APICALL EXPORT std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, + const bool invert = false); diff --git a/include/toml++/toml.hpp b/include/libcufetch/toml++/toml.hpp similarity index 99% rename from include/toml++/toml.hpp rename to include/libcufetch/toml++/toml.hpp index e0e21663..31e9ea84 100644 --- a/include/toml++/toml.hpp +++ b/include/libcufetch/toml++/toml.hpp @@ -2636,7 +2636,7 @@ TOML_NAMESPACE_START }; TOML_NODISCARD - constexpr std::string_view get_line(std::string_view doc, source_index line_num) noexcept + constexpr optional<std::string_view> get_line(std::string_view doc, source_index line_num) noexcept { if (line_num == 0) { @@ -3658,7 +3658,7 @@ TOML_NAMESPACE_START { TOML_NODISCARD TOML_ALWAYS_INLINE - path operator"" _tpath(const char* str, size_t len) + path operator""_tpath(const char* str, size_t len) { return path(std::string_view{ str, len }); } @@ -9712,7 +9712,7 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_ALWAYS_INLINE - parse_result operator"" _toml(const char* str, size_t len) + parse_result operator""_toml(const char* str, size_t len) { return parse(std::string_view{ str, len }); } @@ -9721,7 +9721,7 @@ TOML_NAMESPACE_START TOML_NODISCARD TOML_ALWAYS_INLINE - parse_result operator"" _toml(const char8_t* str, size_t len) + parse_result operator""_toml(const char8_t* str, size_t len) { return parse(std::u8string_view{ str, len }); } @@ -12750,7 +12750,7 @@ TOML_ANON_NAMESPACE_START return value; } }; - static_assert(std::is_trivial_v<utf8_codepoint>); + static_assert(std::is_trivially_default_constructible_v<utf8_codepoint> && std::is_trivially_copyable_v<utf8_codepoint>); static_assert(std::is_standard_layout_v<utf8_codepoint>); struct TOML_ABSTRACT_INTERFACE utf8_reader_interface @@ -16342,7 +16342,7 @@ TOML_ANON_NAMESPACE_START std::ifstream file; TOML_OVERALIGNED char file_buffer[sizeof(void*) * 1024u]; file.rdbuf()->pubsetbuf(file_buffer, sizeof(file_buffer)); -#if TOML_WINDOWS +#if TOML_WINDOWS && !(defined(__MINGW32__) || defined(__MINGW64__)) file.open(impl::widen(file_path_str).c_str(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate); #else file.open(file_path_str, std::ifstream::in | std::ifstream::binary | std::ifstream::ate); diff --git a/include/libs/fmt b/include/libs/fmt new file mode 120000 index 00000000..1149671d --- /dev/null +++ b/include/libs/fmt @@ -0,0 +1 @@ +../libcufetch/fmt \ No newline at end of file diff --git a/include/libs/getopt_port/getopt.h b/include/libs/getopt_port/getopt.h new file mode 100644 index 00000000..ac2f991b --- /dev/null +++ b/include/libs/getopt_port/getopt.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Kim Grasman nor the + * names of contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#ifndef INCLUDED_GETOPT_PORT_H +#define INCLUDED_GETOPT_PORT_H + +#if defined(__cplusplus) +extern "C" { +#endif + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +extern char* optarg; +extern int optind, opterr, optopt; + +struct option { + const char* name; + int has_arg; + int* flag; + int val; +}; + +int getopt(int argc, char* const argv[], const char* optstring); + +int getopt_long(int argc, char* const argv[], + const char* optstring, const struct option* longopts, int* longindex); + +#if defined(__cplusplus) +} +#endif + +#endif // INCLUDED_GETOPT_PORT_H diff --git a/include/json.h b/include/libs/json.h similarity index 100% rename from include/json.h rename to include/libs/json.h diff --git a/include/rapidxml-1.13/rapidxml.hpp b/include/libs/rapidxml-1.13/rapidxml.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml.hpp rename to include/libs/rapidxml-1.13/rapidxml.hpp diff --git a/include/rapidxml-1.13/rapidxml_iterators.hpp b/include/libs/rapidxml-1.13/rapidxml_iterators.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_iterators.hpp rename to include/libs/rapidxml-1.13/rapidxml_iterators.hpp diff --git a/include/rapidxml-1.13/rapidxml_print.hpp b/include/libs/rapidxml-1.13/rapidxml_print.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_print.hpp rename to include/libs/rapidxml-1.13/rapidxml_print.hpp diff --git a/include/rapidxml-1.13/rapidxml_utils.hpp b/include/libs/rapidxml-1.13/rapidxml_utils.hpp similarity index 100% rename from include/rapidxml-1.13/rapidxml_utils.hpp rename to include/libs/rapidxml-1.13/rapidxml_utils.hpp diff --git a/include/stb_image.h b/include/libs/stb_image.h similarity index 100% rename from include/stb_image.h rename to include/libs/stb_image.h diff --git a/include/switch_fnv1a.hpp b/include/libs/switch_fnv1a.hpp similarity index 93% rename from include/switch_fnv1a.hpp rename to include/libs/switch_fnv1a.hpp index 2d44ea57..4a5c0f90 100644 --- a/include/switch_fnv1a.hpp +++ b/include/libs/switch_fnv1a.hpp @@ -164,19 +164,19 @@ using fnv1a64 = fnv1a<64>; // we ain't gonna use this, so change to 16 bits instead using fnv1a128 = fnv1a<16>; -constexpr fnv1a16::Type operator"" _fnv1a16(const char* s, const std::size_t l) +constexpr fnv1a16::Type operator""_fnv1a16(const char* s, const std::size_t l) { return fnv1a16::hash(s, l); } -constexpr fnv1a32::Type operator"" _fnv1a32(const char* s, const std::size_t l) +constexpr fnv1a32::Type operator""_fnv1a32(const char* s, const std::size_t l) { return fnv1a32::hash(s, l); } -constexpr fnv1a64::Type operator"" _fnv1a64(const char* s, const std::size_t l) +constexpr fnv1a64::Type operator""_fnv1a64(const char* s, const std::size_t l) { return fnv1a64::hash(s, l); } -constexpr fnv1a128::Type operator"" _fnv1a128(const char* s, const std::size_t l) +constexpr fnv1a128::Type operator""_fnv1a128(const char* s, const std::size_t l) { return fnv1a128::hash(s, l); } @@ -187,7 +187,7 @@ constexpr fnv1a128::Type operator"" _fnv1a128(const char* s, const std::size_t l //static_assert("hello"_fnv1a128 == Pack128(0xe3e1efd54283d94f, 0x7081314b599d31b3)); using strhash = fnv1a128; -constexpr strhash::Type operator"" _strhash(const char* s, const std::size_t l) +constexpr strhash::Type operator""_strhash(const char* s, const std::size_t l) { return strhash::hash(s, l); } @@ -245,7 +245,7 @@ static constexpr strhash::Type hash(const std::basic_string_view<C>& str) } // namespace strhash_lower // Case-insensitive version -constexpr strhash::Type operator"" _strhash_lower(const char* s, const std::size_t l) +constexpr strhash::Type operator""_strhash_lower(const char* s, const std::size_t l) { return strhash_lower::hash(s, l); } diff --git a/include/libs/tiny-process-library/process.hpp b/include/libs/tiny-process-library/process.hpp new file mode 100644 index 00000000..49999e6d --- /dev/null +++ b/include/libs/tiny-process-library/process.hpp @@ -0,0 +1,184 @@ +#ifndef TINY_PROCESS_LIBRARY_HPP_ +#define TINY_PROCESS_LIBRARY_HPP_ +#include <functional> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <unordered_map> +#include <vector> +#ifndef _WIN32 +#include <sys/wait.h> +#endif + +namespace TinyProcessLib { +/// Additional parameters to Process constructors. +struct Config { + /// Buffer size for reading stdout and stderr. Default is 131072 (128 kB). + std::size_t buffer_size = 131072; + /// Set to true to inherit file descriptors from parent process. Default is false. + /// On Windows: has no effect unless read_stdout==nullptr, read_stderr==nullptr and open_stdin==false. + bool inherit_file_descriptors = false; + + /// If set, invoked when process stdout is closed. + /// This call goes after last call to read_stdout(). + std::function<void()> on_stdout_close = nullptr; + /// If set, invoked when process stderr is closed. + /// This call goes after last call to read_stderr(). + std::function<void()> on_stderr_close = nullptr; + + /// On Windows only: controls how the process is started, mimics STARTUPINFO's wShowWindow. + /// See: https://docs.microsoft.com/en-us/windows/desktop/api/processthreadsapi/ns-processthreadsapi-startupinfoa + /// and https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-showwindow + enum class ShowWindow { + hide = 0, + show_normal = 1, + show_minimized = 2, + maximize = 3, + show_maximized = 3, + show_no_activate = 4, + show = 5, + minimize = 6, + show_min_no_active = 7, + show_na = 8, + restore = 9, + show_default = 10, + force_minimize = 11 + }; + /// On Windows only: controls how the window is shown. + ShowWindow show_window{ShowWindow::show_default}; + + /// Set to true to break out of flatpak sandbox by prepending all commands with `/usr/bin/flatpak-spawn --host` + /// which will execute the command line on the host system. + /// Requires the flatpak `org.freedesktop.Flatpak` portal to be opened for the current sandbox. + /// See https://docs.flatpak.org/en/latest/flatpak-command-reference.html#flatpak-spawn. + bool flatpak_spawn_host = false; +}; + +/// Platform independent class for creating processes. +/// Note on Windows: it seems not possible to specify which pipes to redirect. +/// Thus, at the moment, if read_stdout==nullptr, read_stderr==nullptr and open_stdin==false, +/// the stdout, stderr and stdin are sent to the parent process instead. +class Process { +public: +#ifdef _WIN32 + typedef unsigned long id_type; // Process id type + typedef void *fd_type; // File descriptor type +#ifdef UNICODE + typedef std::wstring string_type; +#else + typedef std::string string_type; +#endif +#else + typedef pid_t id_type; + typedef int fd_type; + typedef std::string string_type; +#endif + typedef std::unordered_map<string_type, string_type> environment_type; + +private: + class Data { + public: + Data() noexcept; + id_type id; +#ifdef _WIN32 + void *handle{nullptr}; +#endif + int exit_status{-1}; + }; + +public: + /// Starts a process with the environment of the calling process. + Process(const std::vector<string_type> &arguments, const string_type &path = string_type(), + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with the environment of the calling process. + Process(const string_type &command, const string_type &path = string_type(), + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + + /// Starts a process with specified environment. + Process(const std::vector<string_type> &arguments, + const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; + /// Starts a process with specified environment. + Process(const string_type &command, + const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}) noexcept; +#ifndef _WIN32 + /// Starts a process with the environment of the calling process. + /// Supported on Unix-like systems only. + /// Since the command line is not known to the Process object itself, + /// this overload does not support the flatpak_spawn_host configuration. + Process(const std::function<void()> &function, + std::function<void(const char *bytes, size_t n)> read_stdout = nullptr, + std::function<void(const char *bytes, size_t n)> read_stderr = nullptr, + bool open_stdin = false, + const Config &config = {}); +#endif + __attribute__((visibility("default"))) ~Process() noexcept; + + /// Get the process id of the started process. + id_type get_id() const noexcept; + /// Wait until process is finished, and return exit status. + int get_exit_status() noexcept; + /// If process is finished, returns true and sets the exit status. Returns false otherwise. + bool try_get_exit_status(int &exit_status) noexcept; + /// Write to stdin. + bool write(const char *bytes, size_t n); + /// Write to stdin. Convenience function using write(const char *, size_t). + bool write(const std::string &str); + /// Close stdin. If the process takes parameters from stdin, use this to notify that all parameters have been sent. + void close_stdin() noexcept; + + /// Kill the process. force=true is only supported on Unix-like systems. + void kill(bool force = false) noexcept; + /// Kill a given process id. Use kill(bool force) instead if possible. force=true is only supported on Unix-like systems. + static void kill(id_type id, bool force = false) noexcept; +#ifndef _WIN32 + /// Send the signal signum to the process. + void signal(int signum) noexcept; +#endif + +private: + Data data; + bool closed; + std::mutex close_mutex; + std::function<void(const char *bytes, size_t n)> read_stdout; + std::function<void(const char *bytes, size_t n)> read_stderr; +#ifndef _WIN32 + std::thread stdout_stderr_thread; +#else + std::thread stdout_thread, stderr_thread; +#endif + bool open_stdin; + std::mutex stdin_mutex; + + Config config; + + std::unique_ptr<fd_type> stdout_fd, stderr_fd, stdin_fd; + + id_type open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment = nullptr) noexcept; + id_type open(const string_type &command, const string_type &path, const environment_type *environment = nullptr) noexcept; +#ifndef _WIN32 + id_type open(const std::function<void()> &function) noexcept; +#endif + void async_read() noexcept; + void close_fds() noexcept; +}; + +} // namespace TinyProcessLib + +#endif // TINY_PROCESS_LIBRARY_HPP_ diff --git a/include/libs/toml++ b/include/libs/toml++ new file mode 120000 index 00000000..f28312c2 --- /dev/null +++ b/include/libs/toml++ @@ -0,0 +1 @@ +../libcufetch/toml++ \ No newline at end of file diff --git a/include/utf8/checked.h b/include/libs/utf8/checked.h similarity index 97% rename from include/utf8/checked.h rename to include/libs/utf8/checked.h index 13311551..41e25960 100644 --- a/include/utf8/checked.h +++ b/include/libs/utf8/checked.h @@ -265,7 +265,13 @@ namespace utf8 // The iterator class template <typename octet_iterator> - class iterator : public std::iterator <std::bidirectional_iterator_tag, uint32_t> { + class iterator { + public: + using iterator_category = std::bidirectional_iterator_tag; + using value_type = uint32_t; + using difference_type = std::ptrdiff_t; + using pointer = uint32_t*; + using reference = uint32_t&; octet_iterator it; octet_iterator range_start; octet_iterator range_end; diff --git a/include/utf8/core.h b/include/libs/utf8/core.h similarity index 100% rename from include/utf8/core.h rename to include/libs/utf8/core.h diff --git a/include/parse.hpp b/include/parse.hpp index d243daca..ded8f4d9 100644 --- a/include/parse.hpp +++ b/include/parse.hpp @@ -1,120 +1,42 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _PARSE_HPP #define _PARSE_HPP -#include <string> -#include <unordered_map> -#include <variant> -#include <vector> - -#include "config.hpp" +#include "libcufetch/parse.hh" -// from query.hpp -using systemInfo_t = - std::unordered_map<std::string, std::unordered_map<std::string, std::variant<std::string, size_t, double>>>; - -/* The additional args that parse() needs for getting the necessary infos/configs. - * Only used for making the argument passing more clear. - * Always pass it non-const and by reference - */ -struct parse_args_t -{ - systemInfo_t& systemInfo; - std::string& pureOutput; - std::vector<std::string>& layout; - std::vector<std::string>& tmp_layout; - const Config& config; - const colors_t& colors; - bool parsingLayout; - bool firstrun_clr = true; - bool no_more_reset = false; - std::string endspan = ""; // only for ANDROID_APP -}; - -/* Parse input, in-place, with data from systemInfo. - * Documentation on formatting is in the default config.toml file or the customfetch.1 manual. - * @param input The string to parse - * @param systemInfo The system infos - * @param pureOutput The output of the string but without tags - * @param layout The layout of customfetch - * @param tmp_layout The temponary layout to be used for $<auto> modules - * @param config The config - * @param colors The colors - * @param parsingLayout If we are parsing layout or not - * @param no_more_reset If we are recursively parsing, e.g we are inside tags - */ -std::string parse(std::string input, systemInfo_t& systemInfo, std::string& pureOutput, - std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, - const Config& config, const colors_t& colors, const bool parsingLayout, bool& no_more_reset); - -// parse() for parse_args_t& arguments -std::string parse(const std::string& input, parse_args_t& parse_args); // some times we don't want to use the original pureOutput, // so we have to create a tmp string just for the sake of the function arguments std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args); -/* Set module members values to a systemInfo_t map. - * If the name of said module matches any module name, it will be added - * else, error out. - * @param moduleName The module name - * @param moduleMemberName The module member name - * @param parse_args The parse() like arguments - */ -void addValueFromModuleMember(const std::string& moduleName, const std::string& moduleMemberName, - parse_args_t& parse_args); - -/* Set module only values to a systemInfo_t map. - * If the name of said module matches any module name, it will be added - * else, error out. - * @param moduleName The module name - * @param parse_args The parse() like arguments - */ -void addValueFromModule(const std::string& moduleName, parse_args_t& parse_args); - /* - * Return an info module member value - * @param systemInfo The systemInfo_t map + * Return an info module value + * @param parse_args The parse() like arguments * @param moduleName The module name - * @param moduleMemberName The module member name */ -std::string getInfoFromName(const systemInfo_t& systemInfo, const std::string_view moduleName, - const std::string_view moduleMemberName); - - -std::string get_and_color_percentage(const float& n1, const float& n2, parse_args_t& parse_args, - const bool invert = false); - -// Function to combine multiple fmt::text_style arguments -template <typename... Styles> -void append_styles(fmt::text_style& current_style, Styles&&... styles) -{ - current_style |= (styles | ...); -} - -inline std::vector<std::string> auto_colors; +std::string getInfoFromName(parse_args_t& parse_args, const std::string& moduleName); #endif diff --git a/include/pci.ids.hpp b/include/pci.ids.hpp index e8a20b6d..f84a10e0 100644 --- a/include/pci.ids.hpp +++ b/include/pci.ids.hpp @@ -1,6 +1,8 @@ #ifndef _PCI_IDS_HPP #define _PCI_IDS_HPP +/* TODO: delete any subdevices (what the fuck man this REALLY needs a rewrite) */ + #include "platform.hpp" #if !CF_ANDROID @@ -2674,7 +2676,7 @@ inline std::string get_pci_ids() 174b d340 Radeon R7 340 OEM 1b0a 90d3 Radeon R7 240 OEM 6613 Oland PRO [Radeon R7 240/340 / Radeon 520] - 148c 7340 Radeon R7 340 + XXXX XXXX Radeon R7 340 1682 7240 R7 240 2048 MB 1dcf 3000 Oland PRO [Radeon R7 240/340 / Radeon 520] 6617 Oland LE [Radeon R7 240] @@ -39891,13 +39893,13 @@ C ff Unassigned class )"; } -inline const std::string& all_ids = get_pci_ids(); +extern const std::string& all_ids; inline constexpr std::array<std::string_view, 2424> pci_vendors_array = get_pci_vendors_array(); inline constexpr std::array<int, 2424> pci_vendors_location_array = get_pci_vendors_location_array(); #else -inline const std::string& all_ids = {}; +extern const std::string& all_ids; inline constexpr std::array<std::string_view, 2424> pci_vendors_array = {}; inline constexpr std::array<int, 2424> pci_vendors_location_array = {}; diff --git a/include/platform.hpp b/include/platform.hpp index 6070e2b2..4656d13a 100644 --- a/include/platform.hpp +++ b/include/platform.hpp @@ -1,7 +1,38 @@ -#ifndef _PLATFORM_H -#define _PLATFORM_H +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ -#if (defined(__ANDROID__) || defined(ANDROID_API) || ANDROID_APP) +#ifndef _PLATFORM_H_ +#define _PLATFORM_H_ + +#ifndef __clang__ +# define __is_target_vendor(x) 0 +# define __is_target_os(x) 0 +# define __is_target_environment(x) 0 +#endif + +#if (defined(__ANDROID__) || defined(ANDROID_API) || __is_target_environment(Android)) # define CF_ANDROID 1 #else # define CF_ANDROID 0 @@ -13,14 +44,23 @@ # define CF_LINUX 0 #endif -#if (defined(_WIN32) || defined(_WIN64) || defined(__CYGWIN__)) +#if (defined(__MACOS__) || defined(__MACH__) || defined(TARGET_OS_MAC) || defined(TARGET_OS_OSX) || \ + __is_target_vendor(apple) || __is_target_os(darwin) || __is_target_os(MacOSX)) +# define CF_MACOS 1 +#else +# define CF_MACOS 0 +#endif + +#if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__) || \ + defined(__MINGW32__) || defined(__MINGW64__)) # define CF_WINDOWS 1 #else # define CF_WINDOWS 0 #endif -#if !(CF_LINUX || CF_ANDROID) || CF_WINDOWS -# warning "Platform currently may not be supported, only Linux and Android" +#if !(CF_LINUX || CF_ANDROID || CF_MACOS) || CF_WINDOWS +# warning \ + "Platform currently may not be supported, only Linux, Android and MacOS. Please feel free to report any compilation errors" #endif -#endif // _PLATFORM_H +#endif // _PLATFORM_H_ diff --git a/include/query.hpp b/include/query.hpp deleted file mode 100644 index 34e7544f..00000000 --- a/include/query.hpp +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef _QUERY_HPP -#define _QUERY_HPP - -#include <cstdint> -#include <fstream> -#include <string> -#include <unordered_map> -#include <variant> -#include <vector> - -#include "config.hpp" -#include "parse.hpp" -#include "util.hpp" - -extern "C" { -#include <mntent.h> -#include <pwd.h> -#include <sys/stat.h> -#include <sys/statvfs.h> -#include <sys/sysinfo.h> -#include <sys/utsname.h> -#include <unistd.h> -} - -// Special variable for storing info modules values -using systemInfo_t = - std::unordered_map<std::string, std::unordered_map<std::string, std::variant<std::string, size_t, double>>>; -// used in systemInfo_t most of the time -using variant = std::variant<std::string, size_t, double>; - -inline bool is_live_mode = false; - -#if !ANDROID_APP -#define CHECK_INIT(x) if (x || is_live_mode) -#else -#define CHECK_INIT(x) if (true) -#endif - -namespace Query -{ - -enum -{ - DISK_VOLUME_TYPE_HIDDEN = 1 << 2, - DISK_VOLUME_TYPE_REGULAR = 1 << 3, - DISK_VOLUME_TYPE_EXTERNAL = 1 << 4, - DISK_VOLUME_TYPE_READ_ONLY = 1 << 5, -}; - -class System -{ -public: - struct System_t - { - std::string os_pretty_name{ UNKNOWN }; - std::string os_name{ UNKNOWN }; - std::string os_id{ UNKNOWN }; - std::string os_version_id{ UNKNOWN }; - std::string os_version_codename{ UNKNOWN }; - std::string os_initsys_name{ UNKNOWN }; - std::string os_initsys_version{ UNKNOWN }; - - std::string host_modelname{ UNKNOWN }; - std::string host_version{ UNKNOWN }; - std::string host_vendor{ UNKNOWN }; - - std::string pkgs_installed{ UNKNOWN }; - }; - - System(); - - std::string kernel_name() noexcept; - std::string kernel_version() noexcept; - std::string hostname() noexcept; - std::string arch() noexcept; - std::string& os_pretty_name() noexcept; - std::string& os_name() noexcept; - std::string& os_id() noexcept; - std::string& os_initsys_name(); - std::string& os_initsys_version(); - std::string& os_versionid() noexcept; - std::string& os_version_codename() noexcept; - long& uptime() noexcept; - - // motherboard (host) - std::string& host_modelname() noexcept; - std::string& host_vendor() noexcept; - std::string& host_version() noexcept; - - std::string& pkgs_installed(const Config& config); - -private: - static System_t m_system_infos; - static bool m_bInit; - static struct utsname m_uname_infos; - static struct sysinfo m_sysInfos; -}; - -class User -{ -public: - struct User_t - { - std::string shell_path{ MAGIC_LINE }; - std::string shell_name{ MAGIC_LINE }; - std::string shell_version{ UNKNOWN }; - std::string wm_name{ MAGIC_LINE }; - std::string wm_version{ UNKNOWN }; - std::string de_name{ MAGIC_LINE }; - std::string de_version{ UNKNOWN }; - std::string term_name{ MAGIC_LINE }; - std::string term_version{ MAGIC_LINE }; - // private: - std::string m_wm_path; - }; - - User() noexcept; - - std::string name() noexcept; - std::string shell_path() noexcept; - std::string& shell_name() noexcept; - std::string& shell_version(const std::string_view shell_name); - std::string& wm_name(bool dont_query_dewm, const std::string_view term_name); - std::string& wm_version(bool dont_query_dewm, const std::string_view term_name); - std::string& de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name); - std::string& de_version(const std::string_view de_name); - std::string& term_name(); - std::string& term_version(const std::string_view term_name); - - static bool m_bDont_query_dewm; - -private: - static bool m_bInit; - static User_t m_users_infos; - static struct passwd* m_pPwd; -}; - -class Theme -{ -public: - struct Theme_t - { - std::string gtk_theme_name{ MAGIC_LINE }; - std::string gtk_icon_theme{ MAGIC_LINE }; - std::string gtk_font{ MAGIC_LINE }; - std::string cursor{ MAGIC_LINE }; - std::string cursor_size{ UNKNOWN }; - }; - - Theme(const std::uint8_t ver, systemInfo_t& queried_themes, const std::string& theme_name_version, - const Config& config, const bool gsettings_only = false); - - Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only = false); - - std::string gtk_theme() noexcept; - std::string gtk_icon_theme() noexcept; - std::string gtk_font() noexcept; - std::string& cursor() noexcept; - std::string& cursor_size() noexcept; - -private: - User query_user; - std::string m_wmde_name; - std::string m_theme_name; - systemInfo_t& m_queried_themes; - static Theme_t m_theme_infos; -}; - -class CPU -{ -public: - struct CPU_t - { - std::string name{ UNKNOWN }; - std::string nproc{ UNKNOWN }; - - double freq_max = 0; - double freq_min = 0; - double freq_cur = 0; - double freq_bios_limit = 0; - double temp = 0; - - // private: - double freq_max_cpuinfo = 0; - // only in Android - std::string modelname; - std::string vendor; - }; - - CPU() noexcept; - - std::string& name() noexcept; - std::string& nproc() noexcept; - - // only in Android - std::string& vendor() noexcept; - std::string& modelname() noexcept; - - double& freq_max() noexcept; - double& freq_min() noexcept; - double& freq_cur() noexcept; - double& freq_bios_limit() noexcept; - double& temp() noexcept; - -private: - static bool m_bInit; - static CPU_t m_cpu_infos; -}; - -class GPU -{ -public: - struct GPU_t - { - std::string name{ UNKNOWN }; - std::string vendor{ UNKNOWN }; - }; - - GPU(const std::string& id, systemInfo_t& queried_gpus); - - std::string& name() noexcept; - std::string& vendor() noexcept; - -private: - uint16_t m_vendor_id; - uint16_t m_device_id; - std::string m_vendor_id_s; - std::string m_device_id_s; - - static GPU_t m_gpu_infos; -}; - -class Battery -{ -public: - struct Battery_t - { - std::string modelname{ MAGIC_LINE }; - std::string vendor{ MAGIC_LINE }; - std::string status{ MAGIC_LINE }; - std::string technology{ UNKNOWN }; - std::string capacity_level{ UNKNOWN }; - double temp{ 0 }; - double perc{ 0 }; - }; - - Battery(); - - std::string& modelname() noexcept; - std::string& vendor() noexcept; - std::string& status() noexcept; - std::string& technology() noexcept; - std::string& capacity_level() noexcept; - double& perc() noexcept; - double& temp() noexcept; - -private: - static bool m_bInit; - static Battery_t m_battery_infos; -}; - -class Disk -{ -public: - struct Disk_t - { - double total_amount = 0; - double free_amount = 0; - double used_amount = 0; - int types_disk = 0; - std::string typefs { MAGIC_LINE }; - std::string device { MAGIC_LINE }; - std::string mountdir{ MAGIC_LINE }; - }; - - Disk(const std::string& path, systemInfo_t& queried_paths, parse_args_t& parse_args, - const bool auto_module = false); - - double& total_amount() noexcept; - double& free_amount() noexcept; - double& used_amount() noexcept; - int& types_disk() noexcept; - std::string& typefs() noexcept; - std::string& device() noexcept; - std::string& mountdir() noexcept; - - std::vector<std::string>& disks_formats() noexcept - { return m_disks_formats; } - -private: - std::vector<std::string> m_disks_formats, m_queried_devices; - static struct statvfs m_statvfs; - static Disk_t m_disk_infos; -}; - -class RAM -{ -public: - struct RAM_t - { - double total_amount = 0; - double free_amount = 0; - double used_amount = 0; - double swap_free_amount = 0; - double swap_used_amount = 0; - double swap_total_amount = 0; - }; - - RAM() noexcept; - - double& total_amount() noexcept; - double& free_amount() noexcept; - double& used_amount() noexcept; - double& swap_free_amount() noexcept; - double& swap_used_amount() noexcept; - double& swap_total_amount() noexcept; - -private: - static bool m_bInit; - static RAM_t m_memory_infos; -}; - -} // namespace Query - -// inline Query::System query_system; -// inline Query::CPU query_cpu; -// inline Query::GPU query_gpu; -// inline Query::RAM query_ram; - -#endif diff --git a/include/texts.hpp b/include/texts.hpp new file mode 100644 index 00000000..1bef9adc --- /dev/null +++ b/include/texts.hpp @@ -0,0 +1,536 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _TEXTS_HPP_ +#define _TEXTS_HPP_ + +#include "platform.hpp" +#include <string_view> + +// cufetchpm +inline constexpr std::string_view cufetchpm_help = (R"(Usage: cufetchpm <COMMAND> [OPTIONS]... +Manage plugins for customfetch. + +Terms: + REPO: + - With install: a Git repository, either URL or local path, both containing plugins and a 'cufetchpm.toml' manifest. + - With enable OR disable: the name of a repository already installed (as listed with 'cufetchpm list'). + +Examples: + Install a plugin repository from GitHub: + cufetchpm install https://github.com/Toni500github/customfetch-plugins-github + Disable a plugin from an installed repository: + cufetchpm disable customfetch-plugins-github/github-user-fetch + Uninstall an entire plugin repository: + cufetchpm uninstall customfetch-plugins-github + +Commands: + help <COMMAND> Show help for a specific command. + install [OPTIONS] <REPO(s)>... Install one or more plugin repository from a Git repo or local path. + uninstall <REPO(s)>... Uninstall one or more installed plugin repository. + enable <REPO/PLUGIN>... Enable one or more plugins from an installed repository. + disable <REPO/PLUGIN>... Disable one or more plugins from an installed repository. + list Show all plugins installed via state.toml. + update Update and upgrade all repositories + gen-manifest Generate a template 'cufetchpm.toml' file. + +Global options: + -h, --help Show this help message. + -V, --version Show version and build information. + +)"); + +inline constexpr std::string_view cufetchpm_help_install = (R"(Usage: cufetchpm install [OPTIONS] <REPO>... + +Install one or more plugin repositories. If a given argument exists on disk, +it is treated as a local directory. Otherwise, it is treated as a Git +repository URL and will be cloned. + +All plugins found within the repository will be installed. + +Options: + -f, --force Force installation, even if already installed. + -h, --help Show help for this command. +)"); + +inline constexpr std::string_view cufetchpm_help_list = (R"(Usage: cufetchpm list [options] +List all installed plugins. + +Options: + -v, --verbose Show detailed plugin information. + -h, --help Show help for this command. +)"); + +// customfetch + +inline constexpr std::string_view customfetch_help = (R"(Usage: customfetch [OPTIONS]... +A command-line, GUI app, and Android widget system information tool (like neofetch) focused on customizability and performance. + +NOTE: Boolean flags [<BOOL>] accept: "true", 1, "enable", or empty. Any other value is treated as false. + +GENERAL OPTIONS: + -h, --help Print this help menu. + -V, --version Print version and other infos about the build. + -C, --config <PATH> Path to the config file (default: ~/.config/customfetch/config.toml). + + --gen-config [<PATH>] Generate default config file. If PATH is omitted, saves to default location. + Prompts before overwriting. + +LOGO OPTIONS: + -n, --no-logo Disable logo display. + -L, --logo-only Print only the logo (skip layout completely). + -s, --source-path <PATH> Path to custom ASCII art/image file. + + -a, --ascii-logo-type <TYPE> + Type of ASCII art (typically "small", "old", or empty for default). + Example: "-d arch -a older" looks for "arch_older.txt". + + -D, --data-dir <PATH> Path to data directory containing "ascii/" subfolder with distro logos. + -d, --distro <NAME> Use a custom distro logo (case-insensitive, e.g., "windows 11" or "ArCh"). + -p, --logo-position <POS> Logo position: "top" (default), "left", or "bottom". + -o, --offset <NUM> Space between logo and layout (default: 5). + --logo-padding-top <NUM> Logo padding from top (default: 0). + --logo-padding-left <NUM> Logo padding from left (default: 0). + +LAYOUT & FORMATTING: + -m, --layout-line <STRING> Override config layout with custom line(s). + Example: `-m "${auto}OS: $<os.name>" -m "${auto}CPU: $<cpu>"`. + + -N, --no-color Disable all colors (useful for pipes/scripts). + --layout-padding-top <NUM> Layout padding from top (default: 0). + --wrap-lines=[<BOOL>] Enable terminal line wrapping (default: false). + --title-sep <STRING> String to use for $<title.sep> (default: "-"). + --sep-reset <STRING> String that resets color (default: ":"). + --sep-reset-after=[<BOOL>] Reset color after (default) or before 'sep-reset'. + +GUI/TERMINAL OPTIONS: + -f, --font <STRING> GUI font (format: "FAMILY STYLE SIZE", e.g., "Liberation Mono Normal 12"). + -i, --image-backend <NAME> Terminal image backend ("kitty" or "viu"). + --bg-image <PATH> GUI background image path ("disable" to turn off). + +CONFIG: + -O, --override <STRING> Override a config value (non-array). Syntax: "name=value" (no spaces around "="). + Example: "auto.disk.fmt='Disk(%1): %6'". + Note: Names without dots (e.g., "sep-reset-after") gets auto-appended to "config.". + + --color <STRING> Replace a color globally. Syntax: "name=value" (no spaces around "="). + Example: "--color magenta=#FF00FF". + + --disallow-command-tag Do not allow command tags $() to be executed. + This is a safety measure for preventing malicious code to be executed because you didn't want to check the config first. + +INFORMATIONAL: + -l, --list-modules List all available info tag modules (e.g., $<cpu> or $<os.name>). + -w, --how-it-works Explain tags and general customization. + --list-logos List available ASCII logos in --data-dir. + +LIVE MODE: + --loop-ms <NUM> Run in live mode, updating every <NUM> milliseconds (min: 50). + Use inferior <NUM> than 200 to disable. Press 'q' to exit. + +EXAMPLES: + 1. Minimal output with default logo: + customfetch --no-color + + 2. Custom distro logo with live updates: + customfetch --distro "arch" --loop-ms 1000 + + 3. Override layout and colors: + customfetch -m "${magenta}OS: $<os.name>" --color "magenta=#FF00FF" + +For details, see `man customfetch` or run `--how-it-works`. +)"); + +constexpr std::string_view explain_customfetch = (R"( +customfetch is designed for maximum customizability, allowing users to display system information exactly how they want it. +The layout and logo is controlled through special tags that can output system info, execute commands, apply conditional logic, add colors, and calculate percentages with some colors. + +Tag References: +1. Information Tag ($<>) + Retrieves system information from modules. + + Syntax: $<module.submodule.sub...> or $<module> + + Examples: + - $<user.name> # Displays login username + - $<os.kernel.name> # Shows kernel name only + - $<ram> # Shows formatted RAM usage + + Use `--list-modules` to see all available modules and members. + +2. Bash Command Tag ($()) + Executes shell commands and outputs the result. + Supports full shell syntax including pipes and redirection. + + Syntax: $(command) + + Examples: + - $(echo "hello") # Outputs: hello + - $(date +%F) # Shows current date + - $(uname -r | cut -d'-' -f1) # Shows kernel version number only + +3. Conditional Tag ($[]) + Displays different outputs based on conditions. + + Syntax: $[condition,comparison,true_output,false_output] + + Examples: + - $[$<user.name>,toni,Welcome back!,Access denied] + - $[$(date +%m-%d),12-25,Merry Christmas!,] + - $[$<os.name.id>,arch,${green}I use arch btw,${red}Non-arch user] + +4. Color Tag (${}) + Applies colors and text formatting. + + Basic syntax: ${color} or ${modifiers#RRGGBB} + + Color options: + - Named colors from config + - Hex colors: ${#ff00cc} + - Special colors: ${auto} (uses logo colors) + - Reset styles: ${0} (normal), ${1} (bold reset) + + Formatting modifiers (prefix before hexcolor): + - ! = Bold + - u = Underline + - i = Italic + - s = Strikethrough + - l = Blink (terminal only) + - b = Background color + + Advanced GUI-only modifiers: + - o = Overline + - a(value) = Foreground alpha (1-65536 or 0%-100%) + - L(value) = Underline style (none/single/double/low/error) + - U(color) = Underline color (hex) + - B(color) = Background color (hex) + - S(color) = Strikethrough color (hex) + - O(color) = Overline color (hex) + - A(value) = Background alpha (1-65536 or 0%-100%) + - w(value) = Font weight (light/normal/bold/ultrabold or 100-1000) + + Examples: +GUI App only: + ${oU(#ff0000)L(double)}Error # Double red underline + ${a(50%)#00ff00}Semi-transparent green + Cross-platform: + ${\e[1;33m}Bold yellow + ${b#222222}${white}White on gray + ${auto3}The 3rd logo color + + Notes: + - customfetch will try to convert ANSI escape codes to GUI app equivalent + - customfetch will ignore GUI-specific modifiers on terminal. + - if you're using the GUI app and want to display a custom logo that's an image, all the auto colors will be the same colors as the distro ones. + +5. Percentage Tag ($%%) + Calculates and displays colored percentages. + + Syntax: $%value,total% or $%!value,total% (inverted colors) + + Examples: + - $%$<ram.used>,$<ram.total>% + - $%!50,100% (shows red if low) + - $%$(cat /sys/class/power_supply/BAT0/capacity),100% + +Pro Tip: +- Combine tags for powerful formatting: + ${u#5522dd}$[$(date +%H),12,Good ${yellow}morning,Good ${#ff8800}afternoon] + +FAQ: +Q: Why do special characters (&, <) break the GUI display? +A: Escape these characters with \\ (e.g replace "<" with "\\<" from both config and ASCII art): + This doesn't affect terminal output. + +Q: How can I use cbonsai as ASCII art? +A: 1. Create a text file containing: $(!cbonsai -p) + 2. Use: customfetch -s "/path/to/file.txt" + 3. Adjust offset for proper alignment + +Q: Does customfetch support nested tags? +A: Yes! Complex nesting is supported, for example: + $<disk($<disk($[1,1,$(echo -n $<disk(/).mountdir>),23]).mountdir>)> +)"); + +// default customfetch config +inline constexpr std::string_view AUTOCONFIG = R"#([config] + +# For more information on how customfetch works and the layout, +# Read either: +# * -w or --how-it-works +# * the manual customfetch.1 +# * if on the android app, click the button "how it works" during widget configuration +layout = [ + "$<title>", + "$<title.sep>", + "${auto}OS: $<os.name> $<system.arch>", + "${auto}Host: $<system.host>", + "${auto}Kernel: $<os.kernel>", + "${auto}Uptime: $<os.uptime>", + "${auto}Terminal: $<user.terminal>", + "${auto}Shell: $<user.shell>",)#" +#if !CF_ANDROID + R"#( + "${auto}Theme: $<theme.gtk.all.name>", + "${auto}Icons: $<theme.gtk.all.icon>", + "${auto}Font: $<theme.gtk.all.font>", + "${auto}Cursor: $<theme.cursor>", + "${auto}WM: $<user.wm.name>", + "${auto}DE: $<user.de.name>",)#" +#endif + R"#( + "$<auto.disk>", + "${auto}Swap: $<swap>", + "${auto}CPU: $<cpu>", + "${auto}GPU: $<gpu>", + "${auto}RAM: $<ram>", + "", + "$<colors>", # normal colors + "$<colors.light>" # light colors +] + +# display ascii-art or image/gif (GUI only) near layout +# put "os" for displaying the OS ascii-art +# or the "/path/to/file" for displaying custom files +# or "off" for disabling ascii-art or image displaying +source-path = "os" + +# Path to where we'll take all the distros/OSs ascii arts. +# note: it MUST contain an "ascii" subdirectory +data-dir = "/usr/share/customfetch" + +# The type of ASCII art to apply ("small", "old"). +# Basically will add "_<type>" to the logo filename. +# It will return the regular linux ascii art if it doesn't exist. +# Leave empty it for regular. +ascii-logo-type = "" + +# A char (or string) to use in $<title_sep> +title-sep = "-" + +# A separator (or string) that when encountered, will automatically +# reset color, aka. automatically add ${0} (only in layout) +# Make it empty for disabling +sep-reset = ":" + +# Should we reset color after or before the separator? +# true = after ("test ->${0} ") +# false = before ("test ${0}-> ") +sep-reset-after = false + +# Where the logo should be displayed. +# Values: "top" or "left" or "bottom" +logo-position = "left" + +# Offset between the ascii art and the layout +# Can also be rapresented as a %, but super unstable sometimes. +offset = "5" + +# Padding between the start and the ascii art +logo-padding-left = 0 + +# Padding of the ascii art from the top +logo-padding-top = 0 + +# Padding of the layout from the top +layout-padding-top = 0 + +# Usually in neofetch/fastfetch, when your terminal size is too small, +# to render some text in 1 line, they don't wrap those lines, instead they truncate them. +# Enable/Disable if you want this +wrap-lines = false + +# Used in disk, ram and swap modules. +# If true, we're going to use the SI standard byte unit (1kB == 1000 bytes) +# Else if false, we using the IEC byte unit (1KiB == 1024 bibytes) +# Really nerdy stuff +use-SI-byte-unit = false + +# Warn against tradeoffs between slower queries for availability +# e.g. falling back to gsettings when we can't find the config file for GTK +slow-query-warnings = false + +# Colors in the terminal (for Desktop/Android app, use the ones under [gui]) +black = "\e[1;30m" +red = "\e[1;31m" +green = "\e[1;32m" +yellow = "\e[1;33m" +blue = "\e[1;34m" +magenta = "\e[1;35m" +cyan = "\e[1;36m" +white = "\e[1;37m" + +# Alias colors. Basically more color variables. +# They can be used as like as the color tag. +# This is as like as using the --add-color argument +# Syntax must be "name=value", e.g "purple=magenta" or "orange=!#F08000" +alias-colors = ["purple=magenta"] + +# Colors to be used in percentage tag and modules members. +# They are used as if you're using the color tag. +# It's an array just for "convenience" +# 1st color for good +# 2nd color for normal +# 3rd color for bad +percentage-colors = ["green", "yellow", "red"] + +# $<auto.disk> config +[auto.disk] +# Format for displaying the auto detected disks infos +# %1 = mount directory +# %2 = device path +# %3 = type of filesystem +# %4 = total amount of storage +# %5 = free amount of storage +# %6 = used amount of storage +# %7 = percentage of used storage +# %8 = percentage of free storage +fmt = "${auto}Disk (%1): $<disk(%1)>" + +# Only print disks that matches the description +# of the following types: +# regular = Regular disks (internel M.2 SSD, ...) (won't be specified) +# external = External disks (USB, SATA, ...) +# read-only = Disks with read-only filesystems +# hidden = Disks that are not really mounted by the user +display-types = ["regular", "external", "read-only"] + +# In some OSes such as NixOS or Android, there might be some directories that are bind mounted. +# Bind mounted directories create an additional view of an existing directory, +# and `statfs()` on the mount point will return the filesystem statistics of the original directory. +show-duplicated = false + +# $<os.uptime> config +[os.uptime] +# how to display the name of the uptime +# e.g: hours = "hrs" -> "Uptime: 3hrs" +days = " days" +hours = " hours" +mins = " mins" +secs = " secs" + +# $<os.pkgs> config +[os.pkgs] +# Ordered list of which packages installed count should be displayed in $<os.pkgs> +# remember to not enter the same name twice, else the world will finish +# Choices: pacman, flatpak, dpkg, apk +# +# Pro-tip: if your package manager isn't listed here, yet, +# use the bash command tag in the layout +# e.g "Packages: $(pacman -Q | wc -l) (pacman)" +pkg-managers = ["pacman", "dpkg", "flatpak"] + +# Distros and package manager specific +# package manager paths for getting the packages count from path. +# They are arrays so you can add multiple paths. +# +# If you don't know what these ares, leave them by default settings +pacman-dirs = ["/var/lib/pacman/local/"] +dpkg-files = ["/var/lib/dpkg/status", "/data/data/com.termux/files/usr/var/lib/dpkg/status"] +flatpak-dirs = ["/var/lib/flatpak/app/", "~/.local/share/flatpak/app/"] +apk-files = ["/var/lib/apk/db/installed"] + +# Desktop/Android app options +[gui] + +# These are the colors you can use in the GUI mode. +# They overwrite the terminal colors from above. +# They can only have hexcodes colors and its modifiers +black = "!#000005" +red = "!#ff2000" +green = "!#00ff00" +blue = "!#00aaff" +cyan = "!#00ffff" +yellow = "!#ffff00" +magenta = "!#f881ff" +white = "!#ffffff" + +# Path to image as a background. +# put "disable" for disabling and use the theme color as background. +bg-image = "disable" + +# Path to gtk css file to be used. +# put "disable" for disabling. +gtk-css = "disable" + +)#"; + +inline constexpr std::string_view AUTO_MANIFEST = R"([repository] +# The repositry name. +# It must contain only alpha-numeric characters and symbols such as '-' or '_' +name = "repo_name" + +# The repository git clone / homepage url. +url = "https://github.com/user/repo" + +# Platform-dependent packages required to build all plugins in this repository. +# NOTE: This will ONLY tell the user which packages are missing; +# it will not actually install them. +# +# Use the "all" key for dependencies common to every platform. +# Current platforms: all, linux, macos, android +[dependencies] +all = ["pkg-config", "cmake"] +linux = ["wayland-protocols", "xorg-dev"] +android = ["ndk-build"] +macos = ["gtk+3"] + +# From now on, each table that is neiter "repository" nor "dependencies" will be treated as a plugin entry. +# The tables' names still must conform alpha-numeric characters and symbols such as '-' or '_' +[test-plugin] + +# The plugin description. +description = "Test plugin" + +# The plugin authors. +authors = ["user1", "friend_user1"] + +# The plugin SPDX License Identifiers (not validated) +licenses = ["MIT", "GPL-2.0"] + +# A list of registered root modules the plugin can provide. +prefixes = ["github", "git"] + +# What platforms are supported by the plugin (case-sensitive). +# Use just ["all"] for cross-platform plugins. +# Current platforms: all, linux, macos, android +platforms = ["all"] + +# The directory where the final plugin output shall be seen. +# It will be evaluated/checked after building the plugin library. +# The path is relative to the repository root unless absolute. +output-dir = "build/plugin-dir/" + +# A list of commands to be executed for building the plugin. +# All commands are executed in a single shared shell session, +# so environment variables, `cd`, and other shell state persist across steps. +# Commands are executed in order and stop at the first failure. +build-steps = [ + "make -C ./test-plugin-entry/", + "mkdir -p ./build/plugin-dir/", + "mv ./test-plugin-entry/library.so ./build/plugin-dir/library.so" +])"; + +#endif // !_TEXTS_HPP_ diff --git a/include/util.hpp b/include/util.hpp index 0c4d6597..221e2db6 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -30,7 +30,6 @@ #include <sys/stat.h> #include <sys/types.h> -#include <chrono> #include <cstdlib> #include <filesystem> #include <iostream> @@ -38,15 +37,14 @@ #include <vector> #include "fmt/base.h" -#include "fmt/chrono.h" #include "fmt/color.h" -#include "fmt/os.h" +#include "libcufetch/common.hh" #include "platform.hpp" // clang-format off // Get string literal length constexpr std::size_t operator""_len(const char*, std::size_t ln) noexcept -{ +{ return ln; } @@ -57,7 +55,7 @@ struct byte_units_t double num_bytes; }; -#if ENABLE_NLS +#if ENABLE_NLS && !CF_MACOS /* here so it doesn't need to be included elsewhere */ #include <libintl.h> #include <locale.h> @@ -66,81 +64,71 @@ struct byte_units_t #define _(s) (char*)s #endif -constexpr const char NOCOLOR[] = "\033[0m"; -constexpr const char NOCOLOR_BOLD[] = "\033[0m\033[1m"; -constexpr const char UNKNOWN[] = "(unknown)"; +/* lib = library to load (string) */ +#define LOAD_LIBRARY(lib) dlopen(lib, RTLD_NOW); -// Usually in neofetch/fastfetch when some infos couldn't be queried, -// they remove it from the display. With customfetch is kinda difficult to know when to remove -// the info to display, since it's all modular with tags, so I have created -// magic line to be sure that I don't cut the wrong line. -// -// Every instance of this string in a layout line, the whole line will be erased. -constexpr const char MAGIC_LINE[] = "(cut this line NOW!! RAHHH)"; - -/* lib = library to load (string) - * code = code to execute if anything goes wrong - */ -#define LOAD_LIBRARY(lib, code) \ - void* handle = dlopen(lib, RTLD_LAZY); \ - if (!handle) \ - code; - -/* ret_type = type of what the function returns +/* handler = the library handle + * ret_type = type of what the function returns * func = the function name * ... = the arguments in a function if any */ -#define LOAD_LIB_SYMBOL(ret_type, func, ...) \ - typedef ret_type (*func##_t)(__VA_ARGS__); \ - func##_t func = reinterpret_cast<func##_t>(dlsym(handle, #func)); +#define LOAD_LIB_SYMBOL(handler, ret_type, func, ...) \ + typedef ret_type (*func##_t)(__VA_ARGS__); \ + func##_t func = reinterpret_cast<func##_t>(dlsym(handler, #func)); -#define UNLOAD_LIBRARY() dlclose(handle); +#define UNLOAD_LIBRARY(handle) dlclose(handle); -#define BOLD_COLOR(x) (fmt::emphasis::bold | fmt::fg(x)) +#if CF_WINDOWS + constexpr char LIBRARY_EXTENSION[] = ".dll"; +#elif CF_MACOS + constexpr char LIBRARY_EXTENSION[] = ".dylib"; +#else + constexpr char LIBRARY_EXTENSION[] = ".so"; +#endif + +inline bool is_live_mode = false; /* https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c#874160 * Check if substring exists at the end * @param fullString The string to lookup * @param ending The string to check at the end of fullString */ -bool hasEnding(const std::string_view fullString, const std::string_view ending); +EXPORT bool hasEnding(const std::string_view fullString, const std::string_view ending); /* https://stackoverflow.com/questions/874134/find-out-if-string-ends-with-another-string-in-c#874160 * Check if substring exists at the start * @param fullString The string to lookup * @param start The string to check at the start of fullString */ -bool hasStart(const std::string_view fullString, const std::string_view start); +EXPORT bool hasStart(const std::string_view fullString, const std::string_view start); -std::vector<std::string> split(const std::string_view text, char delim); +/* Spilt a string into a vector using a delimeter + * @param text The string to split + * @param delim The delimeter used for spliting the text + */ +EXPORT std::vector<std::string> split(const std::string_view text, const char delim); /* Get device name from `all_ids` in pci.ids.hpp * @param dev_entry_pos Line position from where the device is located */ -std::string name_from_entry(size_t dev_entry_pos); +EXPORT std::string name_from_entry(size_t dev_entry_pos); /* Get vendor device name from `all_ids` in pci.ids.hpp * @param vendor_entry_pos Line position from where the device is located * @param vendor_id_s The vendor ID (e.g 10de) */ -std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_view vendor_id_s); +EXPORT std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_view vendor_id_s); /* Function to perform binary search on the pci vendors array to find a device from a vendor. * @param vendor_id_s The vendor ID (e.g 10de) * @param pci_id_s The device ID (e.g 1f82) */ -std::string binarySearchPCIArray(const std::string_view vendor_id_s, const std::string_view pci_id_s); +EXPORT std::string binarySearchPCIArray(const std::string_view vendor_id_s, const std::string_view pci_id_s); /* Function to perform binary search on the pci vendors array to find a vendor. * @param vendor_id_s The vendor ID (e.g 10de) */ -std::string binarySearchPCIArray(const std::string_view vendor_id_s); - -/* http://stackoverflow.com/questions/478898/ddg#478960 - * Execute shell command and read its output from stdout. - * @param cmd The command to execute - */ -std::string read_shell_exec(const std::string_view cmd); +EXPORT std::string binarySearchPCIArray(const std::string_view vendor_id_s); /* Get file value from a file and trim quotes and double-quotes * @param iterIndex The iteration index used for getting the necessary value only tot times @@ -148,45 +136,38 @@ std::string read_shell_exec(const std::string_view cmd); * @param str The string to assign the trimmed value, inline * @param amount The amount to be used in the line.substr() (should be used with something like "foobar"_len) */ -void getFileValue(u_short& iterIndex, const std::string_view line, std::string& str, const size_t& amount); +EXPORT void getFileValue(u_short& iterIndex, const std::string_view line, std::string& str, const size_t& amount); /* Covert bytes (or bibytes) to be accurate to the max prefix (maxprefix or YB/YiB) * @param num The number to convert * @param base Base to devide (1000 = bytes OR 1024 = bibytes) * @param maxprefix The maxinum prefix we can go up to (empty for ignore) */ -byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix = ""); +EXPORT byte_units_t auto_divide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix = ""); /* Covert bytes (or bibytes) to be accurate to a specific prefix * @param num The number to convert * @param prefix The prefix we want to convert to (GiB, MB, etc.) */ -byte_units_t devide_bytes(const double num, const std::string_view prefix); +EXPORT byte_units_t divide_bytes(const double num, const std::string_view prefix); /* Check if file is image (static or gif). * Doesn't check for mp4, mp3 or other binary formats * @param bytes The header bytes of the file */ -bool is_file_image(const unsigned char* bytes); +EXPORT bool is_file_image(const unsigned char* bytes); /* Write error message and exit if EOF (or CTRL-D most of the time) * @param cin The std::cin used for getting the input */ -void ctrl_d_handler(const std::istream& cin); +EXPORT void ctrl_d_handler(const std::istream& cin); /* Replace special symbols such as ~ and $ (at the begging) in std::string * @param str The string * @param dont Don't do it * @return The modified string */ -std::string expandVar(std::string ret, bool dont = false); - -/* Executes commands with execvp() and keep the program running without existing - * @param cmd_str The command to execute - * @param exitOnFailure Whether to call exit(1) on command failure. - * @return true if the command successed, else false - */ -bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_print = true); +EXPORT std::string expandVar(std::string ret, bool dont = false); /* Get a relative path from an enviroment variable (PATH, XDG_DATA_DIRS, ...) * Either path of an executable, directory, etc... @@ -195,22 +176,22 @@ bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_p * @param mode Mode of the file/directory using the enums declared in sys/stat.h * Such as S_IXUSR for executables, S_IFDIR for directories, etc. */ -std::string get_relative_path(const std::string_view relative_path, const std::string_view env, const long long mode); +EXPORT std::string get_relative_path(const std::string_view relative_path, const std::string_view env, const long long mode); /* Simulate behaviour of the command `which` * @param command The command to lookup in the $PATH */ -std::string which(const std::string_view command); +EXPORT std::string which(const std::string_view command); /* Get file path from $XDG_DATA_DIRS * @param file The file to lookup in the env */ -std::string get_data_path(const std::string_view file); +EXPORT std::string get_data_path(const std::string_view file); /* Get directory path from $XDG_DATA_DIRS * @param dir The directory to lookup in the env */ -std::string get_data_dir(const std::string_view dir); +EXPORT std::string get_data_dir(const std::string_view dir); /* Read a binary file and get its current line, * which simulates the behaviour of the command `strings` but one line at the time @@ -232,7 +213,7 @@ std::string get_data_dir(const std::string_view dir); * } * return false; */ -bool read_binary_file(std::ifstream& f, std::string& ret); +EXPORT bool read_binary_file(std::ifstream& f, std::string& ret); /* https://gist.github.com/GenesisFR/cceaf433d5b42dcdddecdddee0657292 * Replace every instances (inplace) of a substring @@ -240,7 +221,7 @@ bool read_binary_file(std::ifstream& f, std::string& ret); * @param from The substring to lookup to be replaced * @param to The substring to replace in instances of `from` */ -void replace_str(std::string& str, const std::string_view from, const std::string_view to); +EXPORT void replace_str(std::string& str, const std::string_view from, const std::string_view to); /* Executes commands with execvp() and read its output * either from stdout or stderr @@ -250,47 +231,47 @@ void replace_str(std::string& str, const std::string_view from, const std::strin * @param noerror_print Print errors (default true) * @return true if the command successed, else false */ -bool read_exec(std::vector<const char*> cmd, std::string& output, bool useStdErr = false, bool noerror_print = true); +EXPORT bool read_exec(std::vector<std::string> cmd, std::string& output, bool useStdErr = false, bool noerror_print = true); /* Make whole string lowercase * @param str The string to use */ -std::string str_tolower(std::string str); +EXPORT std::string str_tolower(std::string str); /* Make whole string uppercase * @param str The string to use */ -std::string str_toupper(std::string str); +EXPORT std::string str_toupper(std::string str); /* Remove all white spaces (' ', '\t', '\n') from string inplace! * @param input The string to strip - * @original https://github.com/lfreist/hwinfo/blob/main/include/hwinfo/utils/stringutils.h#L50 + * @param padding_only Just trim the string */ -void strip(std::string& input); +EXPORT void strip(std::string& input, bool padding_only = true); /* Read file content (usually from /sys) * and return its first (and only) line, else UNKNOWN * @param path The path of the file to read * @param report_error Report error if any */ -std::string read_by_syspath(const std::string_view path, bool report_error = false); +EXPORT std::string read_by_syspath(const std::string_view path, bool report_error = false); /* Convert hex color (#255224) to a fmt::rgb * @param hexstr The hex color string (must have a '#' at the start) */ -fmt::rgb hexStringToColor(const std::string_view hexstr); +EXPORT fmt::rgb hexStringToColor(const std::string_view hexstr); /* Abbreviate the vendors names * @param vendor The vendor name */ -std::string shorten_vendor_name(std::string vendor); +EXPORT std::string shorten_vendor_name(std::string vendor); /* * Get the user config directory * either from $XDG_CONFIG_HOME or from $HOME/.config/ * @return user's config directory */ -std::string getHomeConfigDir(); +EXPORT std::filesystem::path getHomeConfigDir(); /* * Get the customfetch config directory @@ -298,157 +279,27 @@ std::string getHomeConfigDir(); * from getHomeConfigDir() * @return customfetch's config directory */ -std::string getConfigDir(); +EXPORT std::filesystem::path getConfigDir(); + +/* + * Get the user cache directory + * either from $XDG_CACHE_HOME or from $HOME/.cache/ + * @return user's cache directory + */ +EXPORT std::filesystem::path getHomeCacheDir(); + +/* + * Get the customfetch cache directory + * @return customfetch's cache directory + */ +EXPORT std::filesystem::path getCacheDir(); #if CF_ANDROID /* Get android property name such as "ro.product.marketname" * @param name The property name */ -std::string get_android_property(const std::string_view name); -#endif - -#if !ANDROID_APP -template <typename... Args> -void error(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(stderr, BOLD_COLOR(fmt::rgb(fmt::color::red)), "ERROR: {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - -template <typename... Args> -void die(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(stderr, BOLD_COLOR(fmt::rgb(fmt::color::red)), "FATAL: {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); - std::exit(1); -} - -template <typename... Args> -void debug(const std::string_view fmt, Args&&... args) noexcept -{ -#if DEBUG - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::hot_pink))), "[DEBUG]: {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); +EXPORT std::string get_android_property(const std::string_view name); #endif -} - -template <typename... Args> -void warn(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::yellow))), "WARNING: {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} - -template <typename... Args> -void info(const std::string_view fmt, Args&&... args) noexcept -{ - fmt::print(BOLD_COLOR((fmt::rgb(fmt::color::cyan))), "INFO: {}\n", - fmt::format(fmt::runtime(fmt), std::forward<Args>(args)...)); -} -#else -#include "android/log.h" -#include "jni.h" - -inline struct jni_objects -{ - JNIEnv *env; - jobject obj; -} jni_objs; - - -template <typename... Args> -static void nativeAndFileLog(JNIEnv *env, int log_level, const std::string_view fmt, Args&&... args) -{ - const std::string& fmt_str = fmt::format(fmt::runtime(fmt), args...); - jstring jMessage = env->NewStringUTF(fmt_str.c_str()); - const char *cMessage = env->GetStringUTFChars(jMessage, nullptr); - - __android_log_print(log_level, "customfetch_android", "%s", cMessage); - - env->ReleaseStringUTFChars(jMessage, cMessage); - - if (!std::filesystem::exists(getConfigDir())) - std::filesystem::create_directories(getConfigDir()); - - const std::string& log_file = getConfigDir() + "/log.txt"; - auto now = std::chrono::system_clock::now(); - // reset/delete log.txt if it's older than 5 days - // taken from https://github.com/BurntRanch/TabAUR/blob/main/src/util.cpp#L841 - { - auto timeout_duration = std::chrono::hours(24 * 5); - auto timeout = std::chrono::duration_cast<std::chrono::seconds>(timeout_duration).count(); - std::time_t now_time_t = std::chrono::system_clock::to_time_t(now); - - struct stat file_stat; - if ((stat((log_file).c_str(), &file_stat) != 0) || // failed to open file - (file_stat.st_mtim.tv_sec < now_time_t - timeout)) // file is older than 5 days - fmt::output_file(log_file, fmt::file::CREATE | fmt::file::TRUNC); - } - - auto f = fmt::output_file(log_file, fmt::file::CREATE | fmt::file::APPEND | fmt::file::WRONLY); - f.print("[{:%H:%M:%S}] ", now); - switch(log_level) - { - case ANDROID_LOG_FATAL: f.print("FATAL: {}\n", fmt_str); break; - case ANDROID_LOG_ERROR: f.print("ERROR: {}\n", fmt_str); break; - case ANDROID_LOG_WARN: f.print("WARNING: {}\n", fmt_str); break; - case ANDROID_LOG_INFO: f.print("INFO: {}\n", fmt_str); break; - case ANDROID_LOG_DEBUG: f.print("[DEBUG]: {}\n", fmt_str); break; - } -} - -template <typename... Args> -static void writeToErrorLog(const bool fatal, const std::string_view fmt, Args&&... args) -{ - const std::string& fmt_str = fmt::format(fmt::runtime(fmt), args...); - const std::string_view title = fatal ? "FATAL" : "ERROR"; - - if (!std::filesystem::exists(getConfigDir())) - std::filesystem::create_directories(getConfigDir()); - - auto f = fmt::output_file(getConfigDir() + "/error_log.txt", fmt::file::CREATE | fmt::file::APPEND | fmt::file::RDWR); - auto lock = fmt::output_file(getConfigDir() + "/error.lock"); - auto now = std::chrono::system_clock::now(); - f.print("[{:%H:%M:%S}] {}: {}\n", now, title, fmt_str); - lock.print("[{:%H:%M:%S}] {}: {}\n", now, title, fmt_str); -} - -template <typename... Args> -void error(const std::string_view fmt, Args&&... args) noexcept -{ - nativeAndFileLog(jni_objs.env, ANDROID_LOG_ERROR, fmt, std::forward<Args>(args)...); - writeToErrorLog(false, fmt, std::forward<Args>(args)...); -} - -template <typename... Args> -void die(const std::string_view fmt, Args&&... args) noexcept -{ - nativeAndFileLog(jni_objs.env, ANDROID_LOG_FATAL, fmt, std::forward<Args>(args)...); - writeToErrorLog(true, fmt, std::forward<Args>(args)...); - //exit(1); -} - -template <typename... Args> -void debug(const std::string_view fmt, Args&&... args) noexcept -{ -#if DEBUG - nativeAndFileLog(jni_objs.env, ANDROID_LOG_DEBUG, fmt, std::forward<Args>(args)...); -#endif -} - -template <typename... Args> -void warn(const std::string_view fmt, Args&&... args) noexcept -{ - nativeAndFileLog(jni_objs.env, ANDROID_LOG_WARN, fmt, std::forward<Args>(args)...); -} - -template <typename... Args> -void info(const std::string_view fmt, Args&&... args) noexcept -{ - nativeAndFileLog(jni_objs.env, ANDROID_LOG_INFO, fmt, std::forward<Args>(args)...); -} - -#endif // !ANDROID_APP /** Ask the user a yes or no question. * @param def The default result @@ -457,7 +308,7 @@ void info(const std::string_view fmt, Args&&... args) noexcept * @returns the result, y = true, n = false, only returns def if the result is def */ template <typename... Args> -bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) +EXPORT bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) { const std::string& inputs_str = fmt::format(" [{}]: ", def ? "Y/n" : "y/N"); std::string result; @@ -465,7 +316,7 @@ bool askUserYorN(bool def, const std::string_view fmt, Args&&... args) fmt::print("{}", inputs_str); while (std::getline(std::cin, result) && (result.length() > 1)) - fmt::print(BOLD_COLOR(fmt::rgb(fmt::color::yellow)), "Please answear y or n,{}", inputs_str); + warn("Please answear y or n {}", inputs_str); ctrl_d_handler(std::cin); diff --git a/include/version.h.in b/include/version.h.in new file mode 100644 index 00000000..a77b2468 --- /dev/null +++ b/include/version.h.in @@ -0,0 +1,8 @@ +#pragma once +#define GIT_COMMIT_HASH "@HASH@" +#define GIT_BRANCH "@BRANCH@" +#define GIT_COMMIT_MESSAGE "@MESSAGE@" +#define GIT_COMMIT_DATE "@DATE@" +#define GIT_DIRTY "@DIRTY@" +#define GIT_TAG "@TAG@" +#define GIT_COMMITS "@COMMITS@" diff --git a/libcufetch/Makefile b/libcufetch/Makefile new file mode 100644 index 00000000..b4440677 --- /dev/null +++ b/libcufetch/Makefile @@ -0,0 +1,44 @@ +UNAME_S := $(shell uname -s) + +# is macos? +ifeq ($(UNAME_S),Darwin) + LIBNAME := libcufetch.dylib + INSTALL_NAME := -Wl,-install_name,@rpath/$(LIBNAME) + SHARED_FLAG := -dynamiclib + SONAME_FLAGS := +else + LIBNAME := libcufetch.so.2.0.0 + INSTALL_NAME := -Wl,-soname,libcufetch.so.2 + SHARED_FLAG := -shared + SONAME_FLAGS := -Wl,--export-dynamic +endif + +CXX ?= g++ +GUI_APP ?= 0 +SRC = $(wildcard *.cc) +OBJ = $(SRC:.cc=.o) ../$(BUILDDIR)/toml.o +LDLIBS := ../$(BUILDDIR)/libfmt.a ../$(BUILDDIR)/libtiny-process-library.a +OUTPUT := ../$(BUILDDIR)/$(LIBNAME) +CXXFLAGS += -fvisibility-inlines-hidden -fvisibility=hidden -std=$(CXXSTD) -I../include -I../include/libs -fPIC -DGUI_APP=$(GUI_APP) + +all: $(OUTPUT) + @if [ "$(UNAME_S)" = "Linux" ]; then \ + ln -sf libcufetch.so.2.0.0 ../$(BUILDDIR)/libcufetch.so.2; \ + ln -sf libcufetch.so.2.0.0 ../$(BUILDDIR)/libcufetch.so; \ + elif [ "$(UNAME_S)" = "Darwin" ]; then \ + ln -sf libcufetch.dylib ../$(BUILDDIR)/libcufetch.2.dylib; \ + ln -sf libcufetch.dylib ../$(BUILDDIR)/libcufetch.2.0.0.dylib; \ + fi + +%.o: %.cc + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(OUTPUT): $(OBJ) $(LDLIBS) + $(CXX) $(SHARED_FLAG) $(CXXFLAGS) $(OBJ) \ + $(SONAME_FLAGS) $(INSTALL_NAME) \ + -o $@ $(LDLIBS) + +clean: + rm -f *.o *.so *.a ../$(BUILDDIR)/libcufetch.so + +.PHONY: clean all libcufetch diff --git a/libcufetch/cufetch.cc b/libcufetch/cufetch.cc new file mode 100644 index 00000000..039ef503 --- /dev/null +++ b/libcufetch/cufetch.cc @@ -0,0 +1,17 @@ +#include "libcufetch/cufetch.hh" + +static std::vector<module_t> modules; + +static void addModule(const module_t& module, const std::string& prefix = "") +{ + modules.emplace_back(module).name = prefix + module.name; + + for (const module_t& submodule : module.submodules) + addModule(submodule, prefix + module.name + "."); +} + +/* Register a module, and its submodules, to customfetch. */ +APICALL EXPORT void cfRegisterModule(const module_t& module) { addModule(module); } + +/* Get a list of all modules registered. */ +APICALL EXPORT const std::vector<module_t>& cfGetModules() { return modules; } diff --git a/libcufetch/parse.cc b/libcufetch/parse.cc new file mode 100644 index 00000000..29652593 --- /dev/null +++ b/libcufetch/parse.cc @@ -0,0 +1,833 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "parse.hpp" + +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <cstdlib> +#include <ios> +#include <optional> +#include <sstream> +#include <string> +#include <string_view> +#include <vector> + +#include "tiny-process-library/process.hpp" +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" +#include "libcufetch/cufetch.hh" +#include "fmt/format.h" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +class Parser +{ +public: + Parser(const std::string_view src, std::string& pureOutput) : src{ src }, pureOutput{ pureOutput } {} + + bool try_read(const char c) + { + if (is_eof()) + return false; + + if (src[pos] == c) + { + ++pos; + return true; + } + + return false; + } + + char read_char(const bool add_pureOutput = false) + { + if (is_eof()) + return 0; + + if (add_pureOutput) + pureOutput += src[pos]; + + ++pos; + return src[pos - 1]; + } + + bool is_eof() + { return pos >= src.length(); } + + void rewind(const size_t count = 1) + { pos -= std::min(pos, count); } + + const std::string_view src; + std::string& pureOutput; + size_t dollar_pos = 0; + size_t pos = 0; +}; + +// useless useful tmp string for parse() without using the original +// pureOutput +std::string _; + +#if GUI_APP +// Get span tags from an ANSI escape color such as \e[0;31m +// @param noesc_str The ansi color without \\e[ or \033[ +// @param colors The colors struct we'll look at +// @return An array of 3 span tags elements in the follow: color, weight, type +static std::array<std::string, 3> get_ansi_color(const std::string_view noesc_str, const ConfigBase& config) +{ + const size_t first_m = noesc_str.rfind('m'); + if (first_m == std::string::npos) + die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '{}'"), + noesc_str); + + std::string col{ noesc_str.data() }; + col.erase(first_m); // 1;42 + + std::string weight{ hasStart(col, "1;") ? "bold" : "normal" }; + std::string type{ "fgcolor" }; // either fgcolor or bgcolor + + if (hasStart(col, "1;") || hasStart(col, "0;")) + col.erase(0, 2); + + debug("col = {}", col); + const int n = std::stoi(col); + + if ((n >= 100 && n <= 107) || (n >= 40 && n <= 47)) + type = "bgcolor"; + + // last number + // clang-format off + switch (col.back()) + { + case '0': col = config.getValue<std::string>("gui.black", "!#000005"); break; + case '1': col = config.getValue<std::string>("gui.red", "!#ff2000"); break; + case '2': col = config.getValue<std::string>("gui.green", "!#00ff00"); break; + case '3': col = config.getValue<std::string>("gui.yellow", "!#ffff00"); break; + case '4': col = config.getValue<std::string>("gui.blue", "!#00aaff"); break; + case '5': col = config.getValue<std::string>("gui.magenta", "!#ff11cc"); break; + case '6': col = config.getValue<std::string>("gui.cyan", "!#00ffff"); break; + case '7': col = config.getValue<std::string>("gui.white", "!#ffffff"); break; + } + + if (col.at(0) != '#') + col.erase(0, col.find('#')); + + if ((n >= 100 && n <= 107) || (n >= 90 && n <= 97)) + { + const fmt::rgb color = hexStringToColor(col); + const uint r = color.r * 0.65f + 0xff * 0.35f; + const uint b = color.b * 0.65f + 0xff * 0.35f; + const uint g = color.g * 0.65f + 0xff * 0.35f; + const uint result = (r << 16) | (g << 8) | (b); + + std::stringstream ss; + ss << std::hex << result; + col = ss.str(); + col.insert(0, "#"); + } + + return { col, weight, type }; + // clang-format on +} + +// Convert an ANSI escape RGB color, such as \e[38;2;132;042;231m +// into an hex color string +// @param noesc_str The ansi color without \\e[ or \033[ +// @return The hex equivalent string +static std::string convert_ansi_escape_rgb(const std::string_view noesc_str) +{ + if (std::count(noesc_str.begin(), noesc_str.end(), ';') < 4) + die(_("ANSI escape code color '\\e[{}' should have an rgb type value\n" + "e.g \\e[38;2;255;255;255m"), + noesc_str); + + if (noesc_str.rfind('m') == std::string::npos) + die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '\\e[{}'"), + noesc_str); + + const std::vector<std::string>& rgb_str = split(noesc_str.substr(5), ';'); + + const uint r = std::stoul(rgb_str.at(0)); + const uint g = std::stoul(rgb_str.at(1)); + const uint b = std::stoul(rgb_str.at(2)); + const uint result = (r << 16) | (g << 8) | (b); + + std::stringstream ss; + ss << std::hex << result; + return ss.str(); +} +#endif + +EXPORT std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args) +{ + return parse(input, parse_args.modulesInfo, _, parse_args.layout, parse_args.tmp_layout, parse_args.config, + parse_args.parsingLayout, parse_args.no_more_reset); +} + +EXPORT std::string parse(const std::string& input, parse_args_t& parse_args) +{ + return parse(input, parse_args.modulesInfo, parse_args.pureOutput, parse_args.layout, parse_args.tmp_layout, + parse_args.config, parse_args.parsingLayout, parse_args.no_more_reset); +} + +std::string get_and_color_percentage(const float n1, const float n2, parse_args_t& parse_args, const bool invert) +{ + const std::vector<std::string>& percentage_colors = parse_args.config.getValueArrayStr("config.percentage-colors", {"green", "yellow", "red"}); + const float result = n1 / n2 * static_cast<float>(100); + + std::string color; + if (!invert) + { + if (result <= 45) + color = "${" + percentage_colors.at(0) + "}"; + else if (result <= 80) + color = "${" + percentage_colors.at(1) + "}"; + else + color = "${" + percentage_colors.at(2) + "}"; + } + else + { + if (result <= 45) + color = "${" + percentage_colors.at(2) + "}"; + else if (result <= 80) + color = "${" + percentage_colors.at(1) + "}"; + else + color = "${" + percentage_colors.at(0) + "}"; + } + + return parse(fmt::format("{}{:.2f}%${{0}}", color, result), _, parse_args); +} + +std::string getInfoFromName(parse_args_t& parse_args, const std::string& moduleName) +{ + std::string name; + name.reserve(moduleName.size()); + + /* true when we find a '(' */ + bool collecting = false; + + /* current position */ + size_t i = -1; + size_t stripped_char_count = 0; /* amount of chars stripped from `name` */ + + /* position of start, resets every separator */ + size_t start_pos = 0; + + moduleArgs_t* moduleArgs = new moduleArgs_t; + + /* argument that's collected from what's between the parenthesis in "module(...).test" */ + std::string arg; + arg.reserve(moduleName.size()); + for (const char c : moduleName) + { + i++; + if (c == '(' && !collecting) + { + collecting = true; + continue; + } + + if ((c == '.' || i + 1 == moduleName.size())) + { + if (collecting) + { + if (arg.back() != ')' && c != ')') + die("Module name `{}` is invalid. Arguments must end with )", moduleName); + + if (arg.back() == ')') + arg.pop_back(); + + moduleArgs_t* moduleArg = moduleArgs; + while (moduleArg->next != nullptr) + moduleArg = moduleArg->next; + + moduleArg->name = std::string{ name.begin() + start_pos, name.end() }; + moduleArg->value = arg; + moduleArg->next = new moduleArgs_t; + moduleArg->next->prev = moduleArg; + + if (c == '.') + { + name.push_back('.'); + stripped_char_count++; + } + } + else + { + name.push_back(c); + } + + start_pos = i + 1 - stripped_char_count; + arg = ""; + collecting = false; + + continue; + } + + if (!collecting) + { + name.push_back(c); + } + else + { + stripped_char_count++; + arg.push_back(c); + } + } + + std::string result = "(unknown/invalid module)"; + if (const auto& it = parse_args.modulesInfo.find(name); it != parse_args.modulesInfo.end()) + { + struct callbackInfo_t callbackInfo = { moduleArgs, parse_args }; + + result = it->second.handler(&callbackInfo); + } + + while (moduleArgs) + { + moduleArgs_t* next = moduleArgs->next; + + delete moduleArgs; + + moduleArgs = next; + } + + return result; +} + +std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate = true, const char until = 0); + +std::optional<std::string> parse_conditional_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('[')) + return {}; + + const std::string& condA = parse(parser, parse_args, evaluate, ','); + const std::string& condB = parse(parser, parse_args, evaluate, ','); + + const bool cond = (condA == condB); + + const std::string& condTrue = parse(parser, parse_args, cond, ','); + const std::string& condFalse = parse(parser, parse_args, !cond, ']'); + + return cond ? condTrue : condFalse; +} + +std::optional<std::string> parse_command_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('(')) + return {}; + + std::string command = parse(parser, parse_args, evaluate, ')'); + + if (!evaluate) + return {}; + + if (parse_args.config.getValue("intern.args.disallow-commands", false)) + die(_("Trying to execute command $({}) but --disallow-command-tag is set"), command); + + const bool removetag = (command.front() == '!'); + if (removetag) + command.erase(0, 1); + + std::string cmd_output; + TinyProcessLib::Process proc(command, "", [&](const char* bytes, size_t n){cmd_output.assign(bytes, n);}); + if (!parse_args.parsingLayout && !removetag && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.replace(parser.dollar_pos, command.length() + "$()"_len, cmd_output); + + return cmd_output; +} + +template <typename... Styles> +static void append_styles(fmt::text_style& current_style, Styles&&... styles) +{ + current_style |= (styles | ...); +} + +std::optional<std::string> parse_color_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('{')) + return {}; + + std::string color = parse(parser, parse_args, evaluate, '}'); + + if (!evaluate) + return {}; + + std::string output; + const size_t taglen = color.length() + "${}"_len; + const ConfigBase& config = parse_args.config; + const std::string endspan = !parse_args.firstrun_clr ? "</span>" : ""; + + if (config.getValue("intern.args.disable-colors", false)) + { + if (parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return ""; + } + + // if at end there a '$', it will make the end output "$</span>" and so it will confuse + // addValueFromModule() and so let's make it "$ </span>". this is geniunenly stupid +#if GUI_APP + if (output[0] == '$') + output += ' '; +#endif + + static std::vector<std::string> alias_colors_name, alias_colors_value; + const std::vector<std::string>& alias_colors = config.getValueArrayStr("config.alias-colors", {}); + if (!alias_colors.empty() && (alias_colors_name.empty() && alias_colors_value.empty())) + { + for (const std::string& str : alias_colors) + { + const size_t pos = str.find('='); + if (pos == std::string::npos) + die(_("alias color '{}' does NOT have an equal sign '=' for separating color name and value\n" + "For more check with --help"), str); + + alias_colors_name.push_back(str.substr(0, pos)); + alias_colors_value.push_back(str.substr(pos + 1)); + } + + const auto& it_name = std::find(alias_colors_name.begin(), alias_colors_name.end(), color); + if (it_name != alias_colors_name.end()) + { + const size_t index = std::distance(alias_colors_name.begin(), it_name); + color = alias_colors_value.at(index); + } + } + + static std::vector<std::string> auto_colors; + if (hasStart(color, "auto")) + { + int ver = color.length() > 4 ? std::stoi(color.substr(4)) - 1 : 0; + + if (auto_colors.empty()) + auto_colors.push_back(NOCOLOR_BOLD); + + if (ver < 0 || static_cast<size_t>(ver) >= auto_colors.size()) + ver = 0; + + color = auto_colors.at(ver); + } + +#if GUI_APP + if (color == "1") + { + output += endspan + "<span weight='bold'>"; + } + else if (color == "0") + { + output += endspan + "<span>"; + } +#else + if (color == "1") + { + output += NOCOLOR_BOLD; + } + else if (color == "0") + { + output += NOCOLOR; + } +#endif + else + { + std::string str_clr; +#if GUI_APP + switch (fnv1a16::hash(color)) + { + case "black"_fnv1a16: str_clr = config.getValue<std::string>("gui.black", "!#000005"); break; + case "red"_fnv1a16: str_clr = config.getValue<std::string>("gui.red", "!#ff2000"); break; + case "green"_fnv1a16: str_clr = config.getValue<std::string>("gui.green", "!#00ff00"); break; + case "yellow"_fnv1a16: str_clr = config.getValue<std::string>("gui.yellow", "!#ffff00"); break; + case "blue"_fnv1a16: str_clr = config.getValue<std::string>("gui.blue", "!#00aaff"); break; + case "magenta"_fnv1a16: str_clr = config.getValue<std::string>("gui.magenta", "!#ff11cc"); break; + case "cyan"_fnv1a16: str_clr = config.getValue<std::string>("gui.cyan", "!#00ffff"); break; + case "white"_fnv1a16: str_clr = config.getValue<std::string>("gui.white", "!#ffffff"); break; + default: str_clr = color; break; + } + + const size_t pos = str_clr.rfind('#'); + if (pos != std::string::npos) + { + std::string tagfmt = "span "; + const std::string& opt_clr = str_clr.substr(0, pos); + + size_t argmode_pos = 0; + const auto& append_argmode = [&](const std::string_view fmt, const std::string_view mode) -> size_t { + if (opt_clr.at(argmode_pos + 1) == '(') + { + const size_t closebrak = opt_clr.find(')', argmode_pos); + if (closebrak == std::string::npos) + die(_("'{}' mode in color '{}' doesn't have close bracket"), mode, str_clr); + + const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); + tagfmt += fmt.data() + value + "' "; + + return closebrak; + } + return 0; + }; + + bool bgcolor = false; + for (size_t i = 0; i < opt_clr.length(); ++i) + { + switch (opt_clr.at(i)) + { + case 'b': + bgcolor = true; + tagfmt += "bgcolor='" + str_clr.substr(pos) + "' "; + break; + case '!': tagfmt += "weight='bold' "; break; + case 'u': tagfmt += "underline='single' "; break; + case 'i': tagfmt += "style='italic' "; break; + case 'o': tagfmt += "overline='single' "; break; + case 's': tagfmt += "strikethrough='true' "; break; + + case 'a': + argmode_pos = i; + i += append_argmode("fgalpha='", "fgalpha"); + break; + + case 'A': + argmode_pos = i; + i += append_argmode("bgalpha='", "bgalpha"); + break; + + case 'L': + argmode_pos = i; + i += append_argmode("underline='", "underline option"); + break; + + case 'U': + argmode_pos = i; + i += append_argmode("underline_color='", "colored underline"); + break; + + case 'B': + argmode_pos = i; + i += append_argmode("bgcolor='", "bgcolor"); + break; + + case 'w': + argmode_pos = i; + i += append_argmode("weight='", "font weight style"); + break; + + case 'O': + argmode_pos = i; + i += append_argmode("overline_color='", "overline color"); + break; + + case 'S': + argmode_pos = i; + i += append_argmode("strikethrough_color='", "color of strikethrough line"); + break; + } + } + + if (!bgcolor) + tagfmt += "fgcolor='" + str_clr.substr(pos) + "' "; + + tagfmt.pop_back(); + output += endspan + "<" + tagfmt + ">"; + } + + // "\\e" is for checking in the ascii_art, \033 in the config + else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) + { + const std::string& noesc_str = hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); + debug("noesc_str = {}", noesc_str); + + if (hasStart(noesc_str, "38;2;") || hasStart(noesc_str, "48;2;")) + { + const std::string& hexclr = convert_ansi_escape_rgb(noesc_str); + output += + fmt::format("{}<span {}gcolor='#{}'>", endspan, hasStart(noesc_str, "38") ? 'f' : 'b', hexclr); + } + else if (hasStart(noesc_str, "38;5;") || hasStart(noesc_str, "48;5;")) + { + die(_("256 true color '{}' works only in terminal"), noesc_str); + } + else + { + const std::array<std::string, 3>& clrs = get_ansi_color(noesc_str, config); + const std::string_view color = clrs.at(0); + const std::string_view weight = clrs.at(1); + const std::string_view type = clrs.at(2); + output += fmt::format("{}<span {}='{}' weight='{}'>", endspan, type, color, weight); + } + } + + else + { + error(_("PARSER: failed to parse line with color '{}'"), str_clr); + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return output; + } + +// #if !GUI_APP +#else + switch (fnv1a16::hash(color)) + { + case "black"_fnv1a16: str_clr = config.getValue<std::string>("config.black", "\033[1;30m"); break; + case "red"_fnv1a16: str_clr = config.getValue<std::string>("config.red", "\033[1;31m"); break; + case "green"_fnv1a16: str_clr = config.getValue<std::string>("config.green", "\033[1;32m"); break; + case "yellow"_fnv1a16: str_clr = config.getValue<std::string>("config.yellow", "\033[1;33m"); break; + case "blue"_fnv1a16: str_clr = config.getValue<std::string>("config.blue", "\033[1;34m"); break; + case "magenta"_fnv1a16: str_clr = config.getValue<std::string>("config.magenta", "\033[1;35m"); break; + case "cyan"_fnv1a16: str_clr = config.getValue<std::string>("config.cyan", "\033[1;36m"); break; + case "white"_fnv1a16: str_clr = config.getValue<std::string>("config.white", "\033[1;37m"); break; + default: str_clr = color; break; + } + + const size_t pos = str_clr.rfind('#'); + if (pos != std::string::npos) + { + const std::string& opt_clr = str_clr.substr(0, pos); + + fmt::text_style style; + + const auto& skip_gui_argmode = [&opt_clr](const size_t index) -> size_t { + if (opt_clr.at(index + 1) == '(') + { + const size_t closebrak = opt_clr.find(')', index); + if (closebrak == std::string::npos) + return 0; + + return closebrak; + } + return 0; + }; + + bool bgcolor = false; + for (size_t i = 0; i < opt_clr.length(); ++i) + { + switch (opt_clr.at(i)) + { + case 'b': + bgcolor = true; + append_styles(style, fmt::bg(hexStringToColor(str_clr.substr(pos)))); + break; + case '!': append_styles(style, fmt::emphasis::bold); break; + case 'u': append_styles(style, fmt::emphasis::underline); break; + case 'i': append_styles(style, fmt::emphasis::italic); break; + case 'l': append_styles(style, fmt::emphasis::blink); break; + case 's': append_styles(style, fmt::emphasis::strikethrough); break; + + case 'U': + case 'B': + case 'S': + case 'a': + case 'w': + case 'O': + case 'A': + case 'L': i += skip_gui_argmode(i); break; + } + } + + if (!bgcolor) + append_styles(style, fmt::fg(hexStringToColor(str_clr.substr(pos)))); + + // you can't fmt::format(style, ""); ughh + if (style.has_emphasis()) + { + fmt::detail::ansi_color_escape<char> emph(style.get_emphasis()); + output += emph.begin(); + } + if (style.has_background() || style.has_foreground()) + { + const uint32_t rgb_num = + bgcolor ? style.get_background().value.rgb_color : style.get_foreground().value.rgb_color; + fmt::rgb rgb(rgb_num); + fmt::detail::ansi_color_escape<char> ansi(rgb, bgcolor ? "\x1B[48;2;" : "\x1B[38;2;"); + output += ansi.begin(); + } + } + + // "\\e" is for checking in the ascii_art, \033 in the config + else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) + { + output += "\033["; + output += hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); + } + + else + { + error(_("PARSER: failed to parse line with color '{}'"), str_clr); + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + return output; + } +#endif + + if (!parse_args.parsingLayout && std::find(auto_colors.begin(), auto_colors.end(), color) == auto_colors.end()) + auto_colors.push_back(color); + } + + if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) + parse_args.pureOutput.erase(parser.dollar_pos, taglen); + + parse_args.firstrun_clr = false; + + return output; +} + +std::optional<std::string> parse_info_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('<')) + return {}; + + const std::string& module = parse(parser, parse_args, evaluate, '>'); + + if (!evaluate) + return {}; + + const std::string& info = getInfoFromName(parse_args, module); + + if (parser.dollar_pos != std::string::npos) + parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); + return info; +} + +std::optional<std::string> parse_perc_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('%')) + return {}; + + const std::string& command = parse(parser, parse_args, evaluate, '%'); + + if (!evaluate) + return {}; + + const size_t comma_pos = command.find(','); + if (comma_pos == std::string::npos) + die(_("percentage tag '{}' doesn't have a comma for separating the 2 numbers"), command); + + const bool invert = (command.front() == '!'); + + const float n1 = std::stof(parse(command.substr(invert ? 1 : 0, comma_pos), _, parse_args)); + const float n2 = std::stof(parse(command.substr(comma_pos + 1), _, parse_args)); + + return get_and_color_percentage(n1, n2, parse_args, invert); +} + +std::optional<std::string> parse_tags(Parser& parser, parse_args_t& parse_args, const bool evaluate) +{ + if (!parser.try_read('$')) + return {}; + + if (parser.dollar_pos != std::string::npos) + parser.dollar_pos = parser.pureOutput.find('$', parser.dollar_pos); + + if (const auto& color_tag = parse_color_tag(parser, parse_args, evaluate)) + return color_tag; + + if (const auto& module_tag = parse_info_tag(parser, parse_args, evaluate)) + return module_tag; + + if (const auto& command_tag = parse_command_tag(parser, parse_args, evaluate)) + return command_tag; + + if (const auto& ifTag = parse_conditional_tag(parser, parse_args, evaluate)) + return ifTag; + + if (const auto& perc_tag = parse_perc_tag(parser, parse_args, evaluate)) + return perc_tag; + + parser.rewind(); + return {}; +} + +std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate, const char until) +{ + std::string result; + + while (until == 0 ? !parser.is_eof() : !parser.try_read(until)) + { + if (until != 0 && parser.is_eof()) + { + error(_("PARSER: Missing tag close bracket {} in string '{}'"), until, parser.src); + return result; + } + + if (parser.try_read('\\')) + { + result += parser.read_char(until == 0); + } + else if (const auto& tagStr = parse_tags(parser, parse_args, evaluate)) + { + result += *tagStr; + } + else + { + result += parser.read_char(until == 0); + } + } + + return result; +} + +EXPORT std::string parse(std::string input, const moduleMap_t& modulesInfo, std::string& pureOutput, + std::vector<std::string>& layout, std::vector<std::string>& tmp_layout, const ConfigBase& config, + const bool parsingLayout, bool& no_more_reset) +{ + const std::string& sep_reset = config.getValue<std::string>("config.sep-reset", ":"); + if (!sep_reset.empty() && parsingLayout && !no_more_reset) + { + if (config.getValue("config.sep-reset-after", false)) + replace_str(input, sep_reset, sep_reset + "${0}"); + else + replace_str(input, sep_reset, "${0}" + sep_reset); + + no_more_reset = true; + } + + parse_args_t parse_args{ modulesInfo, pureOutput, layout, tmp_layout, config, parsingLayout, true, no_more_reset }; + Parser parser{ input, pureOutput }; + + std::string ret{ parse(parser, parse_args) }; + +#if GUI_APP + if (!parse_args.firstrun_clr) + ret += "</span>"; + + replace_str(parse_args.pureOutput, " ", " "); + + // escape pango markup + // https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c#L2150 + // workaround: just put "\<" or "\&" in the config, e.g "$<os.kernel> \<- Kernel" + replace_str(ret, "\\<", "<"); + replace_str(ret, "\\&", "&"); + replace_str(ret, "&", "&"); +#else + replace_str(ret, "\\<", "<"); + replace_str(ret, "\\&", "&"); +#endif + + return ret; +} diff --git a/libcufetch/util.cc b/libcufetch/util.cc new file mode 120000 index 00000000..0b83e603 --- /dev/null +++ b/libcufetch/util.cc @@ -0,0 +1 @@ +../src/util.cpp \ No newline at end of file diff --git a/po/customfetch.pot b/po/customfetch.pot index cab1f529..3306ab38 100644 --- a/po/customfetch.pot +++ b/po/customfetch.pot @@ -6,7 +6,7 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: customfetch 1.0.0\n" +"Project-Id-Version: customfetch 2.0.0-beta1\n" "Report-Msgid-Bugs-To: https://github.com/Toni500github/customfetch\n" "POT-Creation-Date: 2025-02-26 19:00+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" diff --git a/po/it_IT.po b/po/it_IT.po index 0b3d79f2..e49437c8 100644 --- a/po/it_IT.po +++ b/po/it_IT.po @@ -5,7 +5,7 @@ # msgid "" msgstr "" -"Project-Id-Version: customfetch 1.0.0\n" +"Project-Id-Version: customfetch 2.0.0-beta1\n" "Report-Msgid-Bugs-To: https://github.com/Toni500github/customfetch\n" "POT-Creation-Date: 2025-02-26 19:00+0100\n" "PO-Revision-Date: 2025-01-02 20:18+0100\n" diff --git a/screenshots/android_widget.jpg b/screenshots/android_widget.jpg deleted file mode 100644 index e6279d05..00000000 Binary files a/screenshots/android_widget.jpg and /dev/null differ diff --git a/screenshots/android_widget2.png b/screenshots/android_widget2.png deleted file mode 100644 index ae3fe7b3..00000000 Binary files a/screenshots/android_widget2.png and /dev/null differ diff --git a/scripts/generateVersion.sh b/scripts/generateVersion.sh new file mode 100755 index 00000000..b2db4af4 --- /dev/null +++ b/scripts/generateVersion.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# Shit stolen from Hyprland, ts script so gud + +# if the git directory doesn't exist, don't gather data to avoid overwriting, unless +# the version file is missing altogether (otherwise compiling will fail) +if [ ! -d ./.git ]; then + if [ -f ./include/version.h ]; then + exit 0 + fi +fi + +cp -fr ./include/version.h.in ./include/version.h + +HASH=${HASH-$(git rev-parse HEAD)} +BRANCH=${BRANCH-$(git branch --show-current)} +MESSAGE=${MESSAGE-$(git log -1 --pretty=%B | head -n 1 | sed -e 's/#//g' -e 's/\"//g')} +DATE=${DATE-$(git show --no-patch --format=%cd --date=local)} +DIRTY=${DIRTY-$(git diff-index --quiet HEAD && echo -n clean || echo -n dirty)} +TAG=${TAG-$(git describe --tags)} +COMMITS=${COMMITS-$(git rev-list --count HEAD)} + +sed -i -e "s#@HASH@#${HASH}#" ./include/version.h +sed -i -e "s#@BRANCH@#${BRANCH}#" ./include/version.h +sed -i -e "s#@MESSAGE@#${MESSAGE}#" ./include/version.h +sed -i -e "s#@DATE@#${DATE}#" ./include/version.h +sed -i -e "s#@DIRTY@#${DIRTY}#" ./include/version.h +sed -i -e "s#@TAG@#${TAG}#" ./include/version.h +sed -i -e "s#@COMMITS@#${COMMITS}#" ./include/version.h diff --git a/scripts/generate_pci_arrays.py b/scripts/generate_pci_arrays.py index 733c6362..7735a01d 100644 --- a/scripts/generate_pci_arrays.py +++ b/scripts/generate_pci_arrays.py @@ -54,6 +54,7 @@ # pci_table[current_vendor][device_id] = device_name continue + # TODO: remove all subdevices (useless) case 2: # subvendor = line.split(' ')[0] # subdevice_id = line.split(' ')[1] diff --git a/src/config.cpp b/src/config.cpp index cdc20e30..7ab55ad5 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -29,38 +29,37 @@ #include <filesystem> #include <string> +#include "texts.hpp" #include "fmt/os.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" #include "util.hpp" -Config::Config(const std::string_view configFile, const std::string_view configDir) +Config::Config(const std::filesystem::path& configFile, const std::filesystem::path& configDir) { if (!std::filesystem::exists(configDir)) { - warn(_("customfetch config folder was not found, Creating folders at {}!"), configDir); + warn(_("customfetch config folder was not found, Creating folders at {}!"), configDir.string()); std::filesystem::create_directories(configDir); } if (!std::filesystem::exists(configFile)) { - warn(_("config file {} not found, generating new one"), configFile); + warn(_("config file {} not found, generating new one"), configFile.string()); this->generateConfig(configFile); } } -void Config::loadConfigFile(const std::string_view filename, colors_t& colors) +void Config::loadConfigFile(const std::filesystem::path& filename) { try { - this->tbl = toml::parse_file(filename); + this->tbl = toml::parse_file(filename.string()); } catch (const toml::parse_error& err) { die(_("Parsing config file '{}' failed:\n" "{}\n" "\t(error occurred at line {} column {})"), - filename, err.description(), + filename.string(), err.description(), err.source().begin.line, err.source().begin.column); } @@ -72,26 +71,23 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->sep_reset_after = getValue<bool>("config.sep-reset-after", false); this->use_SI_unit = getValue<bool>("config.use-SI-byte-unit", false); this->wrap_lines = getValue<bool>("config.wrap-lines", false); - this->offset = getValue<std::uint16_t>("config.offset", 5); this->logo_padding_left = getValue<std::uint16_t>("config.logo-padding-left", 0); this->layout_padding_top = getValue<std::uint16_t>("config.layout-padding-top", 0); this->logo_padding_top = getValue<std::uint16_t>("config.logo-padding-top", 0); - this->sep_reset = getValue<std::string>("config.sep-reset", ":"); - this->ascii_logo_type = getValue<std::string>("config.ascii-logo-type", ""); - this->source_path = getValue<std::string>("config.source-path", "os"); - this->logo_position = getValue<std::string>("config.logo-position", "left"); - this->data_dir = getValue<std::string>("config.data-dir", get_data_dir("customfetch")); - this->title_sep = getValue<std::string>("config.title-sep", "-"); - this->font = getValue<std::string>("gui.font", "Liberation Mono Normal 12"); - this->gui_bg_image = getValue<std::string>("gui.bg-image", "disable"); - - this->auto_disks_fmt = getValue<std::string>("auto.disk.fmt", "${auto}Disk (%1): $<disk(%1)>", true); - this->auto_disks_show_dupl= getValue<bool>("auto.disk.show-duplicated", false); - - this->uptime_d_fmt = getValue<std::string>("os.uptime.days", " days"); - this->uptime_h_fmt = getValue<std::string>("os.uptime.hours", " hours"); - this->uptime_m_fmt = getValue<std::string>("os.uptime.mins", " mins"); - this->uptime_s_fmt = getValue<std::string>("os.uptime.secs", " secs"); + this->offset = expandVar(getValue<std::string>("config.offset", "5")); + this->sep_reset = expandVar(getValue<std::string>("config.sep-reset", ":")); + this->ascii_logo_type = expandVar(getValue<std::string>("config.ascii-logo-type", "")); + this->source_path = expandVar(getValue<std::string>("config.source-path", "os")); + this->logo_position = expandVar(getValue<std::string>("config.logo-position", "left")); + this->data_dir = expandVar(getValue<std::string>("config.data-dir", get_data_dir("customfetch"))); + this->title_sep = expandVar(getValue<std::string>("config.title-sep", "-")); + this->gui_bg_image = expandVar(getValue<std::string>("gui.bg-image", "disable")); + this->gui_css_file = expandVar(getValue<std::string>("gui.gtk-css", "disable")); + + this->uptime_d_fmt = expandVar(getValue<std::string>("os.uptime.days", " days")); + this->uptime_h_fmt = expandVar(getValue<std::string>("os.uptime.hours", " hours")); + this->uptime_m_fmt = expandVar(getValue<std::string>("os.uptime.mins", " mins")); + this->uptime_s_fmt = expandVar(getValue<std::string>("os.uptime.secs", " secs")); this->pkgs_managers= getValueArrayStr("os.pkgs.pkg-managers", {}); this->pacman_dirs = getValueArrayStr("os.pkgs.pacman-dirs", {"/var/lib/pacman/local"}); @@ -99,23 +95,23 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->flatpak_dirs = getValueArrayStr("os.pkgs.flatpak-dirs", {"/var/lib/flatpak/app", "~/.local/share/flatpak/app"}); this->apk_files = getValueArrayStr("os.pkgs.apk-files", {"/var/lib/apk/db/installed"}); - colors.black = getThemeValue("config.black", "\033[1;30m"); - colors.red = getThemeValue("config.red", "\033[1;31m"); - colors.green = getThemeValue("config.green", "\033[1;32m"); - colors.yellow = getThemeValue("config.yellow", "\033[1;33m"); - colors.blue = getThemeValue("config.blue", "\033[1;34m"); - colors.magenta = getThemeValue("config.magenta", "\033[1;35m"); - colors.cyan = getThemeValue("config.cyan", "\033[1;36m"); - colors.white = getThemeValue("config.white", "\033[1;37m"); - - colors.gui_black = getThemeValue("gui.black", "!#000005"); - colors.gui_red = getThemeValue("gui.red", "!#ff2000"); - colors.gui_green = getThemeValue("gui.green", "!#00ff00"); - colors.gui_blue = getThemeValue("gui.blue", "!#00aaff"); - colors.gui_cyan = getThemeValue("gui.cyan", "!#00ffff"); - colors.gui_yellow = getThemeValue("gui.yellow", "!#ffff00"); - colors.gui_magenta = getThemeValue("gui.magenta", "!#ff11cc"); - colors.gui_white = getThemeValue("gui.white", "!#ffffff"); + colors.black = getValue<std::string>("config.black", "\033[1;30m"); + colors.red = getValue<std::string>("config.red", "\033[1;31m"); + colors.green = getValue<std::string>("config.green", "\033[1;32m"); + colors.yellow = getValue<std::string>("config.yellow", "\033[1;33m"); + colors.blue = getValue<std::string>("config.blue", "\033[1;34m"); + colors.magenta = getValue<std::string>("config.magenta", "\033[1;35m"); + colors.cyan = getValue<std::string>("config.cyan", "\033[1;36m"); + colors.white = getValue<std::string>("config.white", "\033[1;37m"); + + colors.gui_black = getValue<std::string>("gui.black", "!#000005"); + colors.gui_red = getValue<std::string>("gui.red", "!#ff2000"); + colors.gui_green = getValue<std::string>("gui.green", "!#00ff00"); + colors.gui_blue = getValue<std::string>("gui.blue", "!#00aaff"); + colors.gui_cyan = getValue<std::string>("gui.cyan", "!#00ffff"); + colors.gui_yellow = getValue<std::string>("gui.yellow", "!#ffff00"); + colors.gui_magenta = getValue<std::string>("gui.magenta", "!#ff11cc"); + colors.gui_white = getValue<std::string>("gui.white", "!#ffffff"); if (this->percentage_colors.size() < 3) { @@ -124,57 +120,12 @@ void Config::loadConfigFile(const std::string_view filename, colors_t& colors) this->percentage_colors = {"green", "yellow", "red"}; } - for (const std::string& str : this->getValueArrayStr("auto.disk.display-types", {"removable", "regular", "read-only"})) - { - switch (fnv1a16::hash(str)) - { - case "removable"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_EXTERNAL; break; - case "regular"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_REGULAR; break; - case "read-only"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_READ_ONLY; break; - case "hidden"_fnv1a16: - this->auto_disks_types |= Query::DISK_VOLUME_TYPE_HIDDEN; break; - } - } - for (const std::string& str : this->getValueArrayStr("config.alias-colors", {})) this->addAliasColors(str); const char *no_color = std::getenv("NO_COLOR"); if (no_color != NULL && no_color[0] != '\0') - this->m_disable_colors = true; -} - -std::string Config::getThemeValue(const std::string_view value, const std::string_view fallback) const -{ - return this->tbl.at_path(value).value<std::string>().value_or(fallback.data()); -} - -std::vector<std::string> Config::getValueArrayStr(const std::string_view value, - const std::vector<std::string>& fallback) -{ - std::vector<std::string> ret; - - // https://stackoverflow.com/a/78266628 - const auto& array = tbl.at_path(value); - if (const toml::array* array_it = array.as_array()) - { - array_it->for_each( - [&ret, value](auto&& el) - { - if (const toml::value<std::string>* str_elem = el.as_string()) - ret.push_back((*str_elem)->data()); - else - warn(_("an element of the array '{}' is not a string"), value); - } - ); - - return ret; - } - else - return fallback; + this->args_disable_colors = true; } void Config::addAliasColors(const std::string& str) @@ -204,7 +155,7 @@ void Config::overrideOption(const std::string& opt) { const size_t pos = opt.find('='); if (pos == std::string::npos) - die(_("alias color '{}' does NOT have an equal sign '=' for separating color name and value\n" + die(_("override option '{}' does NOT have an equal sign '=' for separating config name and value\n" "For more check with --help"), opt); std::string name {opt.substr(0, pos)}; @@ -216,29 +167,27 @@ void Config::overrideOption(const std::string& opt) name.insert(0, "config."); if (value == "true") - overrides[name] = {.value_type = BOOL, .bool_value = true}; + overrides[name] = {BOOL, "", true, 0}; else if (value == "false") - overrides[name] = {.value_type = BOOL, .bool_value = false}; + overrides[name] = {BOOL, "", false, 0}; else if ((value[0] == '"' && value.back() == '"') || (value[0] == '\'' && value.back() == '\'')) - overrides[name] = {.value_type = STR, .string_value = value.substr(1, value.size()-2)}; + overrides[name] = {STR, value.substr(1, value.size()-2), false, 0}; else if (is_str_digital(value)) - overrides[name] = {.value_type = INT, .int_value = std::stoi(value)}; + overrides[name] = {INT, "", false, std::stoi(value)}; else die(_("looks like override value '{}' from '{}' is neither a bool, int or string value"), value, name); } -void Config::generateConfig(const std::string_view filename) +void Config::generateConfig(const std::filesystem::path &filename) { -#if !ANDROID_APP if (std::filesystem::exists(filename)) { - if (!askUserYorN(false, "WARNING: config file '{}' already exists. Do you want to overwrite it?", filename)) + if (!askUserYorN(false, "WARNING: config file '{}' already exists. Do you want to overwrite it?", filename.string())) std::exit(1); } -#endif - auto f = fmt::output_file(filename.data()); + auto f = fmt::output_file(filename.c_str()); f.print("{}", AUTOCONFIG); } diff --git a/src/core-modules/android/battery.cc b/src/core-modules/android/battery.cc new file mode 100644 index 00000000..1a63add9 --- /dev/null +++ b/src/core-modules/android/battery.cc @@ -0,0 +1,148 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include <string> +#include <string_view> +#include <vector> + +#include "core-modules.hh" +#include "json.h" +#include "libcufetch/common.hh" +#include "libcufetch/fmt/format.h" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static json::jobject doc; +static std::vector<std::string> dumpsys; + +static bool assert_doc() +{ + if (doc.size() <= 0) + { + std::string result; + if (!read_exec({ "/data/data/com.termux/files/usr/libexec/termux-api", "BatteryStatus" }, result)) + return false; + if (!json::jobject::tryparse(result.c_str(), doc)) + return false; + } + return true; +} + +static bool assert_dumpsys() +{ + if (dumpsys.size() <= 0) + { + std::string result; + if (!read_exec({ "/system/bin/dumpsys", "battery" }, result)) + return false; + dumpsys = split(result, '\n'); + if (dumpsys[0] != "Current Battery Service state:") + return false; + } + return true; +} + +static std::string read_value_dumpsys(const std::string_view name, const bool is_status = false) +{ + if (!assert_dumpsys()) + return MAGIC_LINE; + + for (size_t i = 1; i < dumpsys.size(); ++i) + { + const size_t pos = dumpsys.at(i).rfind(':'); + const std::string& key = dumpsys.at(i).substr(2, pos); + const std::string& value = dumpsys.at(i).substr(pos + 2); + + if (is_status) + { + if (key.find("powered") != key.npos && value == "true") + return key; + } + + else if (key == name) + return value; + } + + return MAGIC_LINE; +} + +// clang-format off +MODFUNC(battery_modelname) +{ return MAGIC_LINE; } + +MODFUNC(battery_vendor) +{ return MAGIC_LINE; } + +MODFUNC(battery_capacity_level) +{ return MAGIC_LINE; } + +// clang-format on +MODFUNC(battery_status) +{ + if (!assert_doc()) + return read_value_dumpsys("powered", true); + + std::string charge_status{ str_tolower(doc["status"].as_string()) }; + charge_status.at(0) = toupper(charge_status.at(0)); + switch (fnv1a16::hash(doc["plugged"].as_string())) + { + case "PLUGGED_AC"_fnv1a16: return "AC Connected, " + charge_status; + case "PLUGGED_USB"_fnv1a16: return "USB Connected, " + charge_status; + case "PLUGGED_WIRELESS"_fnv1a16: return "Wireless Connected, " + charge_status; + default: return charge_status; + } +} + +MODFUNC(battery_technology) +{ + if (!assert_doc()) + return read_value_dumpsys("technology"); + return MAGIC_LINE; +} + +MODFUNC(battery_perc) +{ + if (!assert_doc()) + { + double level = std::stod(read_value_dumpsys("level")); + double scale = std::stod(read_value_dumpsys("scale")); + if (level > 0 && scale > 0) + return fmt::to_string(level * 100 / scale); + } + + return doc["percentage"]; +} + +double battery_temp() +{ + if (!assert_doc()) + return std::stod(read_value_dumpsys("temperature")) / 10; + return doc["temperature"]; +} + +#endif diff --git a/src/query/android/gpu.cpp b/src/core-modules/android/gpu.cc similarity index 92% rename from src/query/android/gpu.cpp rename to src/core-modules/android/gpu.cc index 6906f6c5..c6d0a395 100644 --- a/src/query/android/gpu.cpp +++ b/src/core-modules/android/gpu.cc @@ -1,5 +1,5 @@ /* - * Copyright 2024 Toni500git + * Copyright 2025 Toni500git * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: @@ -28,11 +28,9 @@ #include <string> -#include "query.hpp" +#include "core-modules.hh" +#include "libcufetch/common.hh" #include "switch_fnv1a.hpp" -#include "util.hpp" - -using namespace Query; // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips static std::string detect_adreno(const std::string& cpu_model_name) @@ -195,25 +193,17 @@ static std::string detect_adreno(const std::string& cpu_model_name) return MAGIC_LINE; } -GPU::GPU(const std::string& id, systemInfo_t& queried_gpus) +// clang-format off +MODFUNC(gpu_name) { - CPU query_cpu; - if (query_cpu.vendor() == "QUALCOMM" || query_cpu.vendor() == "QTI") - { - m_gpu_infos.vendor = "Qualcomm"; - m_gpu_infos.name = detect_adreno(query_cpu.modelname()); - } - else - { - m_gpu_infos.name = m_gpu_infos.vendor = MAGIC_LINE; - } -} + const std::string& vendor = android_cpu_vendor(nullptr); + if (vendor == "QUALCOMM" || vendor == "QTI") + return detect_adreno(android_cpu_model_name(nullptr)); -// clang-format off -std::string& GPU::name() noexcept -{ return m_gpu_infos.name; } + return MAGIC_LINE; +} -std::string& GPU::vendor() noexcept -{ return m_gpu_infos.vendor; } +MODFUNC(gpu_vendor) +{ return android_cpu_vendor(nullptr); } #endif diff --git a/src/query/android/theme.cpp b/src/core-modules/android/os.cc similarity index 58% rename from src/query/android/theme.cpp rename to src/core-modules/android/os.cc index 7a6488b5..0da4ae4d 100644 --- a/src/query/android/theme.cpp +++ b/src/core-modules/android/os.cc @@ -1,5 +1,5 @@ /* - * Copyright 2024 Toni500git + * Copyright 2025 Toni500git * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: @@ -26,39 +26,47 @@ #include "platform.hpp" #if CF_ANDROID -#include "query.hpp" +#include "core-modules.hh" #include "util.hpp" -using namespace Query; +// clang-format off +MODFUNC(os_name) +{ return "Android"; } -Theme::Theme(const std::uint8_t ver, systemInfo_t& queried_themes, const std::string& theme_name_version, - const Config& config, const bool gsettings_only) - : m_queried_themes(queried_themes) -{ - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; -} +MODFUNC(os_pretty_name) +{ return "Android " + os_version_codename(NULL) + " " + os_version_id(NULL); } -Theme::Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only) : m_queried_themes(queried_themes) -{ - m_theme_infos.cursor = m_theme_infos.gtk_font = m_theme_infos.cursor_size = m_theme_infos.gtk_theme_name = m_theme_infos.gtk_icon_theme - = MAGIC_LINE; -} +MODFUNC(os_name_id) +{ return "android"; } -// clang-format off -std::string Theme::gtk_theme() noexcept -{ return m_theme_infos.gtk_theme_name; } +MODFUNC(os_version_id) +{ return get_android_property("ro.build.version.release"); } -std::string Theme::gtk_icon_theme() noexcept -{ return m_theme_infos.gtk_icon_theme; } +MODFUNC(os_version_codename) +{ return get_android_property("ro.build.version.codename"); } -std::string Theme::gtk_font() noexcept -{ return m_theme_infos.gtk_font; } +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } -std::string& Theme::cursor() noexcept -{ return m_theme_infos.cursor; } +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } -std::string& Theme::cursor_size() noexcept -{ return m_theme_infos.cursor_size; } +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ return "init"; } + +MODFUNC(os_initsys_version) +{ return ""; } + +unsigned long os_uptime() +{ + struct std::timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) + return 0; + + return (uint64_t)uptime.tv_sec + (uint64_t)uptime.tv_nsec / 1000000; +} #endif diff --git a/src/core-modules/android/system.cc b/src/core-modules/android/system.cc new file mode 100644 index 00000000..197c0598 --- /dev/null +++ b/src/core-modules/android/system.cc @@ -0,0 +1,67 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" + +#if CF_ANDROID +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "util.hpp" + +static constexpr std::array<std::string_view, 9> vendors_prop_names = { + "ro.product.marketname", "ro.vendor.product.display", "ro.vivo.market.name", + "ro.config.devicename", "ro.config.marketing_name", "ro.product.vendor.model", + "ro.product.oppo_model", "ro.oppo.market.name", "ro.product.brand" +}; + +MODFUNC(host_name) +{ + for (const std::string_view name : vendors_prop_names) + { + const std::string& model_name = get_android_property(name); + if (!model_name.empty()) + return model_name; + } + + return UNKNOWN; +} + +// clang-format off +MODFUNC(host_version) +{ return get_android_property("ro.product.model"); } + +MODFUNC(host_vendor) +{ return get_android_property("ro.product.manufacturer"); } + +MODFUNC(host) +{ return host_vendor(NULL) + " " + host_name(NULL) + " " + host_version(NULL); } + +MODFUNC(arch) +{ return g_uname_infos.machine; } + +#endif diff --git a/src/core-modules/android/theme.cc b/src/core-modules/android/theme.cc new file mode 100644 index 00000000..9fc2e589 --- /dev/null +++ b/src/core-modules/android/theme.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +MODFUNC(theme_gtk_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_font) { return MAGIC_LINE; } +MODFUNC(theme_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_cursor_size) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_icon) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_size) { return MAGIC_LINE; } + +#endif // CF_ANDROID diff --git a/src/core-modules/android/user.cc b/src/core-modules/android/user.cc new file mode 100644 index 00000000..8d3f9ed7 --- /dev/null +++ b/src/core-modules/android/user.cc @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_ANDROID + +#include <linux/limits.h> + +#include <string> + +#include "core-modules.hh" +#include "tiny-process-library/process.hpp" +#include "util.hpp" + +using namespace TinyProcessLib; + +MODFUNC(user_shell_path) +{ + char buf[PATH_MAX]; + return realpath(fmt::format("/proc/{}/exe", getppid()).c_str(), buf); +} + +MODFUNC(user_shell_name) +{ + return user_shell_path(callbackInfo).substr(user_shell_path(callbackInfo).rfind('/') + 1); +} + +MODFUNC(user_shell_version) +{ + const std::string& shell_name = user_shell_name(callbackInfo); + std::string ret; + + if (shell_name == "nu") + Process("nu -c \"version | get version\"", "", [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + else + Process(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data())), "", + [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + + strip(ret); + return ret; +} + +// clang-format off +MMODFUNC(user_name) +{ return g_pwd->pw_name; } + +ODFUNC(user_term_name) +{ return "Termux"; } + +MODFUNC(user_term_version) +{ return getenv("TERMUX_VERSION"); } + +MODFUNC(user_wm_name) +{ return MAGIC_LINE; } + +MODFUNC(user_wm_version) +{ return MAGIC_LINE; } + +MODFUNC(user_de_name) +{ return MAGIC_LINE; } + +MODFUNC(user_de_version) +{ return MAGIC_LINE; } + +#endif diff --git a/src/core-modules/core-modules.cc b/src/core-modules/core-modules.cc new file mode 100644 index 00000000..e22d9ebf --- /dev/null +++ b/src/core-modules/core-modules.cc @@ -0,0 +1,601 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "core-modules.hh" + +#include <dlfcn.h> +#include <unistd.h> + +#include <algorithm> +#include <array> +#include <cstdio> +#include <string> +#include <string_view> +#include <utility> + +#include "config.hpp" +#include "fmt/format.h" +#include "libcufetch/cufetch.hh" +#include "linux/utils/packages.hh" +#include "platform.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +#if !CF_MACOS +#include <mntent.h> +#endif + +using unused = const callbackInfo_t*; + +std::string amount(const double amount, const moduleArgs_t* moduleArgs) +{ + constexpr std::array<std::string_view, 32> sorted_valid_prefixes = { "B", "EB", "EiB", "GB", "GiB", "kB", + "KiB", "MB", "MiB", "PB", "PiB", "TB", + "TiB", "YB", "YiB", "ZB", "ZiB" }; + + if (!moduleArgs->next || moduleArgs->next->value.empty()) + { + byte_units_t amount_unit = auto_divide_bytes(amount, 1024); + return fmt::format("{:.2f} {}", amount_unit.num_bytes, amount_unit.unit); + } + + const std::string& prefix = moduleArgs->next->value; + if (std::binary_search(sorted_valid_prefixes.begin(), sorted_valid_prefixes.end(), prefix)) + return fmt::format("{:.5f}", divide_bytes(amount, prefix).num_bytes); + return "0"; +} + +static std::string get_auto_uptime(const std::uint16_t days, const std::uint16_t hours, const std::uint16_t mins, + const std::uint16_t secs, const Config& config) +{ + if (days == 0 && hours == 0 && mins == 0) + return fmt::format("{}{}", secs, config.uptime_s_fmt); + + std::string ret; + + if (days > 0) + ret += fmt::format("{}{}, ", days, config.uptime_d_fmt); + + if (hours > 0) + ret += fmt::format("{}{}, ", hours, config.uptime_h_fmt); + + if (mins > 0) + ret += fmt::format("{}{}, ", mins, config.uptime_m_fmt); + + ret.erase(ret.length() - 2); // the last ", " + + return ret; +} + +static std::string get_colors_symbol(const callbackInfo_t* callback, bool is_light) +{ + const moduleArgs_t* symbolArg; + for (symbolArg = callback->moduleArgs; symbolArg && symbolArg->name != "symbol"; symbolArg = symbolArg->next) + ; + if (symbolArg->value.empty()) + die( + _("color symbol palette argument module is empty.\n" + "Must be used like 'colors_symbol(`symbol for printing the color palette`)'")); + + if (is_light) + return parse( + fmt::format("${{\033[90m}} {0} ${{\033[91m}} {0} ${{\033[92m}} {0} ${{\033[93m}} {0} ${{\033[94m}} " + "{0} ${{\033[95m}} {0} ${{\033[96m}} {0} ${{\033[97m}} {0} ${{0}}", + symbolArg->value), + callback->parse_args); + else + return parse( + fmt::format("${{\033[30m}} {0} ${{\033[31m}} {0} ${{\033[32m}} {0} ${{\033[33m}} {0} ${{\033[34m}} " + "{0} ${{\033[35m}} {0} ${{\033[36m}} {0} ${{\033[37m}} {0} ${{0}}", + symbolArg->value), + callback->parse_args); +} + +static std::string prettify_de_name(const std::string_view de_name) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "kde"_fnv1a16: + case "plasma"_fnv1a16: + case "plasmashell"_fnv1a16: + case "plasmawayland"_fnv1a16: return "KDE Plasma"; + + case "gnome"_fnv1a16: + case "gnome-shell"_fnv1a16: return "GNOME"; + + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + case "xfce4-session"_fnv1a16: return "Xfce4"; + + case "mate"_fnv1a16: + case "mate-session"_fnv1a16: return "Mate"; + + case "lxqt"_fnv1a16: + case "lxqt-session"_fnv1a16: return "LXQt"; + } + + return de_name.data(); +} + +static std::string prettify_term_name(const std::string_view term_name) +{ + switch (fnv1a16::hash(str_tolower(term_name.data()))) + { + case "gnome-terminal"_fnv1a16: + case "gnome terminal"_fnv1a16: return "GNOME Terminal"; + + case "gnome-console"_fnv1a16: + case "gnome console"_fnv1a16: return "GNOME console"; + } + return term_name.data(); +} + +MODFUNC(disk_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = disk_used(callback); + const double total = disk_total(callback); + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + std::string result {fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + }; + // clang-format on + const std::string& fsname = disk_fsname(callback); + if (fsname != MAGIC_LINE) + result += " - " + fsname; + + const std::string& types = disk_types(callback); + if (!types.empty()) + result += " [" + types + "]"; + + return result; +} + +MODFUNC(ram_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = ram_used(); + const double total = ram_total(); + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + return fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + ; + // clang-format on +} + +MODFUNC(swap_fmt) +{ + const callbackInfo_t* callback = callbackInfo; + const double used = swap_used(); + const double total = swap_total(); + if (used < 1) + return "Disabled"; + + const std::string& perc = get_and_color_percentage(used, total, callback->parse_args, false); + + // clang-format off + return fmt::format("{} / {} {}", + amount(used, callback->moduleArgs), + amount(total, callback->moduleArgs), + parse("${0}(" + perc + ")", callback->parse_args)) + ; + // clang-format on +} + +MODFUNC(battery_fmt) +{ + return fmt::format( + "{} [{}]", get_and_color_percentage(std::stod(battery_perc(callbackInfo)), 100, callbackInfo->parse_args, true), + battery_status(callbackInfo)); +} + +MODFUNC(theme_cursor_fmt) +{ + const std::string& size = theme_cursor_size(callbackInfo); + const std::string& name = theme_cursor_name(callbackInfo); + if (size == UNKNOWN || size == MAGIC_LINE) + return name; + + return fmt::format("{} ({}px)", name, size); +} + +void core_plugins_start(const Config& config) +{ + // ------------ INIT STUFF ------------ + const size_t uptime_secs = os_uptime(); + const size_t uptime_mins = uptime_secs / (60); + const size_t uptime_hours = uptime_secs / (60 * 60); + const size_t uptime_days = uptime_secs / (60 * 60 * 24); + + if (uname(&g_uname_infos) != 0) + die(_("uname() failed: {}\nCould not get system infos"), std::strerror(errno)); + + if (g_pwd = getpwuid(getuid()), !g_pwd) + die(_("getpwent failed: {}\nCould not get user infos"), std::strerror(errno)); + + term_pid = get_terminal_pid(); + term_name = get_terminal_name(); + if (hasStart(str_tolower(term_name), "login") || hasStart(term_name, "init") || hasStart(term_name, "(init)")) + { + is_tty = true; + term_name = ttyname(STDIN_FILENO); + } +#if !CF_MACOS + os_release = fopen("/etc/os-release", "r"); + cpuinfo = fopen("/proc/cpuinfo", "r"); + meminfo = fopen("/proc/meminfo", "r"); + mountsFile = setmntent("/proc/mounts", "r"); +#endif + +#if CF_ANDROID + is_tty = true; +#endif + + // ------------ MODULES REGISTERING ------------ + module_t os_name_pretty_module = {"pretty", "OS pretty name [Ubuntu 22.04.4 LTS; Arch Linux]", {}, os_pretty_name}; + module_t os_name_id_module = {"id", "OS id name [ubuntu, arch]", {}, os_name_id}; + module_t os_name_module = { "name", "OS basic name [Ubuntu]", { + std::move(os_name_pretty_module), + std::move(os_name_id_module) + }, os_name }; + + module_t os_uptime_s_module = {"secs", "uptime of the system in seconds [45]", {}, [=](unused) {return fmt::to_string(uptime_secs % 60);}}; + module_t os_uptime_m_module = {"mins", "uptime of the system in minutes [12]", {}, [=](unused) {return fmt::to_string(uptime_mins % 60);}}; + module_t os_uptime_h_module = {"hours", "uptime of the system in hours [34]", {}, [=](unused) {return fmt::to_string(uptime_hours % 24);}}; + module_t os_uptime_d_module = {"days", "uptime of the system in days [2]", {}, [=](unused) {return fmt::to_string(uptime_days);}}; + module_t os_uptime_module = {"uptime", "(auto) uptime of the system [36 mins, 3 hours, 23 days]", { + std::move(os_uptime_s_module), + std::move(os_uptime_m_module), + std::move(os_uptime_h_module), + std::move(os_uptime_d_module), + }, [=](unused) { return get_auto_uptime(uptime_days, uptime_hours % 24, uptime_mins % 60, + uptime_secs % 60, config); }}; + + module_t os_hostname_module = {"hostname", "hostname of the OS [myMainPC]", {}, os_hostname}; + + module_t os_kernel_name_module = {"name", "kernel name [Linux]", {}, os_kernel_name}; + module_t os_kernel_version_module = {"version", "kernel version [6.9.3-zen1-1-zen]", {}, os_kernel_version}; + module_t os_kernel_module = {"kernel", "kernel name and version [Linux 6.9.3-zen1-1-zen]", { + std::move(os_kernel_name_module), + std::move(os_kernel_version_module) + }, [](unused _) {return os_kernel_name(_) + " " + os_kernel_version(_);}}; + + module_t os_initsys_name_module = {"name", "Init system name [systemd]", {}, os_initsys_name}; + module_t os_initsys_version_module = {"version", "Init system version [256.5-1-arch]", {}, os_initsys_version}; + module_t os_initsys_module = {"initsys", "Init system name and version [systemd 256.5-1-arch]", { + std::move(os_initsys_name_module), + std::move(os_initsys_version_module), + }, [](unused _) {return os_initsys_name(_) + " " + os_initsys_version(_);}}; + + module_t os_pkgs_module = {"pkgs", "Count of system packages", {}, [&](unused){ return get_all_pkgs(config); }}; + + // $<os> + module_t os_module = { "os", "OS modules", { + std::move(os_name_module), + std::move(os_uptime_module), + std::move(os_kernel_module), + std::move(os_hostname_module), + std::move(os_initsys_module), + std::move(os_pkgs_module), + }, NULL}; + cfRegisterModule(os_module); + + // $<system> + module_t host_name_module = {"name", "Host (aka. Motherboard) model name [PRO B550M-P GEN3 (MS-7D95)]", {}, host_name}; + module_t host_version_module = {"version", "Host (aka. Motherboard) model version [1.0]", {}, host_version}; + module_t host_vendor_module = {"vendor", "Host (aka. Motherboard) model vendor [Micro-Star International Co., Ltd.]", {}, host_vendor}; + module_t host_module = {"host", "Host (aka. Motherboard) model name with vendor and version [MSI PRO B550M-P GEN3 (MS-7D95) 1.0]", { + std::move(host_name_module), + std::move(host_version_module), + std::move(host_vendor_module) }, + host}; + + module_t arch_module = {"arch", "the architecture of the machine [x86_64, aarch64]", {}, arch}; + + module_t system_module = { "system", "System modules", { + std::move(host_module), + std::move(arch_module), + }, NULL }; + cfRegisterModule(system_module); + + // $<cpu> + module_t cpu_name_module = {"name", "CPU model name [AMD Ryzen 5 5500]", {}, cpu_name}; + module_t cpu_nproc_module = {"nproc" , "CPU number of virtual processors [12]", {}, cpu_nproc}; + + module_t cpu_freq_cur_module = {"current", "CPU current frequency (in GHz) [3.42]", {}, cpu_freq_cur}; + module_t cpu_freq_max_module = {"max", "CPU maximum frequency (in GHz) [4.90]", {}, cpu_freq_max}; + module_t cpu_freq_min_module = {"min", "CPU minimum frequency (in GHz) [2.45]", {}, cpu_freq_min}; + module_t cpu_freq_bios_module = {"bios_limit", "CPU frequency limited by bios (in GHz) [4.32]", {}, cpu_freq_bios}; + module_t cpu_freq_module = {"freq", "CPU frequency info (GHz)", { + std::move(cpu_freq_cur_module), + std::move(cpu_freq_max_module), + std::move(cpu_freq_min_module), + std::move(cpu_freq_bios_module), + }, cpu_freq_max}; + + module_t cpu_temp_C_module = {"C", "CPU temperature in Celsius [40.62]", {}, [](unused) {return fmt::format("{:.2f}°C", cpu_temp());}}; + module_t cpu_temp_F_module = {"F", "CPU temperature in Fahrenheit [105.12]", {}, [](unused) {return fmt::format("{:.2f}°F", cpu_temp() * 1.8 + 34);}}; + module_t cpu_temp_K_module = {"K", "CPU temperature in Kelvin [313.77]", {}, [](unused) {return fmt::format("{:.2f}°K", cpu_temp() + 273.15);}}; + module_t cpu_temp_module = {"temp", "CPU temperature (by the chosen unit) [40.62]", { + std::move(cpu_temp_C_module), + std::move(cpu_temp_F_module), + std::move(cpu_temp_K_module), + }, [](unused) {return fmt::format("{:.2f}°C", cpu_temp());}}; + + module_t cpu_module = {"cpu", "CPU model name with number of virtual processors and max freq [AMD Ryzen 5 5500 (12) @ 4.90 GHz]",{ + std::move(cpu_name_module), + std::move(cpu_nproc_module), + std::move(cpu_freq_module), + std::move(cpu_temp_module), + }, [](unused _) { + return fmt::format("{} ({}) @ {} GHz", cpu_name(_), cpu_nproc(_), cpu_freq_max(_)); + }}; + cfRegisterModule(cpu_module); + + // $<user> + module_t user_name_module = {"name", "name you are currently logged in (not real name) [toni69]", {}, user_name}; + + module_t user_shell_path_module = {"path", "login shell (with path) [/bin/zsh]", {}, user_shell_path}; + module_t user_shell_name_module = {"name", "login shell [zsh]", {}, user_shell_name}; + module_t user_shell_version_module = {"version", "login shell version (may be not correct) [5.9]", {}, user_shell_version}; + module_t user_shell_module = {"shell", "login shell name and version [zsh 5.9]", { + std::move(user_shell_name_module), + std::move(user_shell_path_module), + std::move(user_shell_version_module), + }, [](unused _) {return user_shell_name(_) + " " + user_shell_version(_);}}; + + module_t user_term_name_module = {"name", "terminal name [alacritty]", {}, [](unused _){ return prettify_term_name(user_term_name(_));}}; + module_t user_term_version_module = {"version", "terminal version [0.13.2]", {}, user_shell_version}; + module_t user_term_module = {"terminal", "terminal name and version [alacritty 0.13.2]", { + std::move(user_term_version_module), + std::move(user_term_name_module) + }, [](unused _) {return user_term_name(_) + " " + user_term_version(_);}}; + + module_t user_wm_name_module = {"name", "Window Manager current session name [dwm; xfwm4]", {}, user_wm_name}; + module_t user_wm_version_module = {"version", "Window Manager version (may not work correctly) [6.2; 4.18.0]", {}, user_wm_version}; + module_t user_wm_module = {"wm", "Window Manager current session name and version", { + std::move(user_wm_version_module), + std::move(user_wm_name_module) + }, [](unused _) {return user_wm_name(_) + " " + user_wm_version(_);}}; + + module_t user_de_name_module = {"name", "Desktop Environment current session name [Plasma]", {}, [](unused _){ return prettify_de_name(user_de_name(_)); }}; + module_t user_de_version_module = {"version", "Desktop Environment version (if available)", {}, user_de_version}; + module_t user_de_module = {"de", "Desktop Environment current session name and version", { + std::move(user_de_version_module), + std::move(user_de_name_module) + }, [](unused _) {return user_de_name(_) + " " + user_de_version(_);}}; + + module_t user_module = {"user", "User modules", { + std::move(user_name_module), + std::move(user_shell_module), + std::move(user_term_module), + std::move(user_wm_module), + std::move(user_de_module), + }, NULL}; + cfRegisterModule(user_module); + + // $<ram> + module_t ram_free_perc_module = {"perc", "percentage of available amount of RAM in total [82.31%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(ram_free(), ram_total(), callback->parse_args, true);}}; + module_t ram_used_perc_module = {"perc", "percentage of used amount of RAM in total [17.69%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(ram_used(), ram_total(), callback->parse_args, false);}}; + module_t ram_free_module = {"free", "available amount of RAM (auto) [10.46 GiB]", {std::move(ram_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(ram_free() * 1024, callback->moduleArgs); }}; + module_t ram_used_module = {"used", "used amount of RAM (auto) [2.81 GiB]", {std::move(ram_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(ram_used() * 1024, callback->moduleArgs); }}; + module_t ram_total_module = {"total", "total amount of RAM (auto) [15.88 GiB]", {}, [](const callbackInfo_t *callback) { return amount(ram_total() * 1024, callback->moduleArgs); }}; + + module_t ram_module = {"ram", "used and total amount of RAM (auto) with used percentage [2.81 GiB / 15.88 GiB (5.34%)]", { + std::move(ram_free_module), + std::move(ram_used_module), + std::move(ram_total_module) + }, ram_fmt}; + cfRegisterModule(ram_module); + + // $<swap> + module_t swap_free_perc_module = {"perc", "percentage of available amount of the swapfile in total [6.71%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(swap_free(), swap_total(), callback->parse_args, true);}}; + module_t swap_used_perc_module = {"perc", "percentage of used amount of the swapfile in total [93.29%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(swap_used(), swap_total(), callback->parse_args, false);}}; + module_t swap_free_module = {"free", "available amount of the swapfile (auto) [34.32 MiB]", {std::move(swap_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(swap_free() * 1024, callback->moduleArgs); }}; + module_t swap_used_module = {"used", "used amount of the swapfile (auto) [477.68 MiB]", {std::move(swap_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(swap_used() * 1024, callback->moduleArgs); }}; + module_t swap_total_module = {"total", "total amount of the swapfile (auto) [512.00 MiB]", {}, [](const callbackInfo_t *callback) { return amount(swap_total() * 1024, callback->moduleArgs); }}; + + module_t swap_module = {"swap", "used and total amount of the swapfile (auto) with used percentage [477.68 MiB / 512.00 MiB (88.45%)]", { + std::move(swap_free_module), + std::move(swap_used_module), + std::move(swap_total_module) + }, swap_fmt}; + cfRegisterModule(swap_module); + + // $<disk> + module_t disk_fsname_module = {"fs", "type of filesystem [ext4]", {}, disk_fsname}; + module_t disk_device_module = {"device", "path to device [/dev/sda5]", {}, disk_device}; + module_t disk_mountdir_module = {"mountdir", "path to the device mount point [/]", {}, disk_mountdir}; + module_t disk_types_module = {"types", "an array of type options (pretty format) [Regular, External]", {}, disk_types}; + + module_t disk_free_perc_module = {"perc", "percentage of available amount of the disk in total [17.82%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(disk_free(callback), disk_total(callback), callback->parse_args, true);}}; + module_t disk_used_perc_module = {"perc", "percentage of used amount of the disk in total [82.18%]", {}, [](const callbackInfo_t *callback) {return get_and_color_percentage(disk_used(callback), disk_total(callback), callback->parse_args, false);}}; + module_t disk_free_module = {"free", "available amount of disk space (auto) [438.08 GiB]", {std::move(disk_free_perc_module)}, [](const callbackInfo_t *callback) { return amount(disk_free(callback), callback->moduleArgs); }}; + module_t disk_used_module = {"used", "used amount of disk space (auto) [360.02 GiB]", {std::move(disk_used_perc_module)}, [](const callbackInfo_t *callback) { return amount(disk_used(callback), callback->moduleArgs); }}; + module_t disk_total_module = {"total", "total amount of disk space (auto) [100.08 GiB]", {}, [](const callbackInfo_t *callback) { return amount(disk_total(callback), callback->moduleArgs); }}; + + module_t disk_module = {"disk", "used and total amount of disk space (auto) with type of filesystem and used percentage [379.83 GiB / 438.08 GiB (86.70%) - ext4]", { + std::move(disk_fsname_module), + std::move(disk_device_module), + std::move(disk_mountdir_module), + std::move(disk_types_module), + std::move(disk_free_module), + std::move(disk_used_module), + std::move(disk_total_module), + }, disk_fmt}; + cfRegisterModule(disk_module); + + // $<battery> + module_t battery_modelname_module = {"name", "battery model name", {}, battery_modelname}; + module_t battery_status_module = {"status", "battery current status [Discharging, AC Connected]", {}, battery_status}; + module_t battery_capacity_module = {"capacity", "battery capacity level [Normal, Critical]", {}, battery_capacity_level}; + module_t battery_technology_module = {"technology", "battery technology [Li-lion]", {}, battery_technology}; + module_t battery_vendor_module = {"manufacturer", "battery manufacturer name", {}, battery_vendor}; + module_t battery_perc_module = {"perc", "battery current percentage", {}, battery_perc}; + + module_t battery_temp_C_module = {"C", "battery temperature in Celsius [e.g. 37.12°C]", {}, [](unused) {return fmt::format("{:.2f}°C", battery_temp());}}; + module_t battery_temp_F_module = {"F", "battery temperature in Fahrenheit [e.g. 98.81°F]", {}, [](unused) {return fmt::format("{:.2f}°F", battery_temp() * 1.8 + 34);}}; + module_t battery_temp_K_module = {"K", "battery temperature in Kelvin [e.g. 310.27°K]", {}, [](unused) {return fmt::format("{:.2f}°K", battery_temp() + 273.15);}}; + module_t battery_temp_module = {"temp", "battery temperature (by the chosen unit)", { + std::move(battery_temp_C_module), + std::move(battery_temp_F_module), + std::move(battery_temp_K_module), + }, [](unused) {return fmt::format("{:.2f}°C", battery_temp());}}; + + module_t battery_module = {"battery", "battery current percentage and status [50.00% [Discharging]]", { + std::move(battery_modelname_module), + std::move(battery_status_module), + std::move(battery_capacity_module), + std::move(battery_technology_module), + std::move(battery_vendor_module), + std::move(battery_perc_module), + std::move(battery_temp_module), + }, battery_fmt}; + cfRegisterModule(battery_module); + + // $<theme> + module_t theme_gtk_all_name_module = {"name", "all GTK theme name [Arc-Dark [GTK2/3/4]]", {}, theme_gtk_all_name}; + module_t theme_gtk_all_font_module = {"font", "all GTK icons theme name [Papirus-Dark [GTK2/3], Qogir [GTK4]]", {}, theme_gtk_all_font}; + module_t theme_gtk_all_icon_module = {"icon", "all GTK fonts theme name [Hack Nerd Font 13 [GTK2], Noto Sans 10 [GTK3/4]]", {}, theme_gtk_all_icon}; + module_t theme_gtk_all_module = {"all", "Auto format module for all GTK versions", { + std::move(theme_gtk_all_name_module), + std::move(theme_gtk_all_font_module), + std::move(theme_gtk_all_icon_module) + }, NULL}; + + module_t theme_gtk_name_module = {"name", "GTK theme name [Arc-Dark]", {}, theme_gtk_name}; + module_t theme_gtk_font_module = {"font", "GTK icons theme name [Qogir-Dark]", {}, theme_gtk_font}; + module_t theme_gtk_icon_module = {"icon", "GTK font theme name [Noto Sans 10]", {}, theme_gtk_icon}; + module_t theme_gtk_module = {"gtk", "GTK module (take as argument the desidered GTK version to query (2,3,4))", { + std::move(theme_gtk_name_module), + std::move(theme_gtk_font_module), + std::move(theme_gtk_icon_module), + std::move(theme_gtk_all_module) + }, NULL}; + + module_t theme_gsettings_name_module = {"name", "Gsettings theme name [Decay-Green]", {}, theme_gsettings_name}; + module_t theme_gsettings_font_module = {"font", "Gsettings icons theme name [Papirus-Dark]", {}, theme_gsettings_font}; + module_t theme_gsettings_icon_module = {"icon", "Gsettings font theme name [Cantarell 10]", {}, theme_gsettings_icon}; + module_t theme_gsettings_cursor_name_module = {"name", "Gsettings cursor name [Bibata-Modern-Ice]", {}, theme_gsettings_cursor_name}; + module_t theme_gsettings_cursor_size_module = {"size", "Gsettings cursor size (in px) [16]", {}, theme_gsettings_cursor_size}; + module_t theme_gsettings_cursor_module = {"cursor", "Gsettings cursor name with its size (if queried) [Bibata-Modern-Ice (16px)]", { + std::move(theme_gsettings_cursor_size_module), + std::move(theme_gsettings_cursor_name_module), + }, NULL}; + module_t theme_gsettings_module = {"gsettings", "If USE_DCONF flag is set, then we're going to use dconf, else backing up to gsettings command", { + std::move(theme_gsettings_name_module), + std::move(theme_gsettings_font_module), + std::move(theme_gsettings_icon_module), + std::move(theme_gsettings_cursor_module) + }, NULL}; + + module_t theme_cursor_name_module = {"name", "cursor name [Bibata-Modern-Ice]", {}, theme_cursor_name}; + module_t theme_cursor_size_module = {"size", "cursor size (in px) [16]", {}, theme_cursor_size}; + module_t theme_cursor_module = {"cursor", "Gsettings cursor name with its size (if queried) [Bibata-Modern-Ice (16px)]", { + std::move(theme_cursor_size_module), + std::move(theme_cursor_name_module), + }, theme_cursor_fmt}; + + module_t theme_module = {"theme", "module used for generic theme stuff such as cursor", { + std::move(theme_gtk_module), + std::move(theme_gsettings_module), + std::move(theme_cursor_module), + }, NULL}; + cfRegisterModule(theme_module); + + // $<gpu> + module_t gpu_name_module = {"name", "GPU model name [GeForce GTX 1650]", {}, gpu_name}; + module_t gpu_vendor_short_module = {"short", "GPU short vendor name [NVIDIA]", {}, [](const callbackInfo_t *callback) { + return shorten_vendor_name(gpu_vendor(callback)); + }}; + module_t gpu_vendor_module = {"vendor", "GPU vendor name [NVIDIA Corporation]", { + std::move(gpu_vendor_short_module) + }, gpu_vendor}; + module_t gpu_module = {"gpu", "GPU shorter vendor name and model name [NVIDIA GeForce GTX 1650]", { + std::move(gpu_name_module), + std::move(gpu_vendor_module) + }, [](const callbackInfo_t *callback) {return shorten_vendor_name(gpu_vendor(callback)) + " " + gpu_name(callback);}}; + cfRegisterModule(gpu_module); + + // $<auto> + module_t auto_disk_module = {"disk", "Query all disks based on auto.disk.display-types", {}, auto_disk}; + module_t auto_module = {"auto", "", {std::move(auto_disk_module)}, NULL}; + cfRegisterModule(auto_module); + + // $<title> + module_t title_sep_module = { "sep", "separator between the title and the system infos (with the title length) [--------]", {}, [&](unused _) { + const size_t title_len = + std::string_view(user_name(_) + "@" + os_hostname(_)).length(); + + std::string str; + str.reserve(config.title_sep.length() * title_len); + for (size_t i = 0; i < title_len; i++) + str += config.title_sep; + + return str; + } }; + module_t title_module = { "title", "user and hostname colored with ${auto2} [toni@arch2]", { std::move(title_sep_module) }, [](const callbackInfo_t* callback) { + return parse("${auto2}$<user.name>${0}@${auto2}$<os.hostname>", callback->parse_args); + } }; + cfRegisterModule(title_module); + + // $<colors> + module_t colors_light_symbol_module = { "symbol", "light color palette with specific symbol", {}, [](const callbackInfo_t* callback) { return get_colors_symbol(callback, true); } }; + module_t colors_symbol_module = { "symbol", "color palette with specific symbol", {}, [](const callbackInfo_t* callback) { return get_colors_symbol(callback, false); }}; + module_t colors_light_module = { "light", "light color palette with background spaces", { std::move(colors_light_symbol_module) }, [](const callbackInfo_t* callback) { + return parse( + "${\033[100m} ${\033[101m} ${\033[102m} ${\033[103m} ${\033[104m} " + " ${\033[105m} ${\033[106m} ${\033[107m} ${0}", callback->parse_args); + } }; + module_t colors_module = { "colors", "color palette with background spaces", + { std::move(colors_symbol_module), std::move(colors_light_module) }, + [](const callbackInfo_t* callback) { + return parse( + "${\033[40m} ${\033[41m} ${\033[42m} ${\033[43m} ${\033[44m} " + "${\033[45m} ${\033[46m} ${\033[47m} ${0}", + callback->parse_args); + } }; + cfRegisterModule(colors_module); +} + +void core_plugins_finish() +{ + if (mountsFile) + fclose(mountsFile); + if (os_release) + fclose(os_release); + if (meminfo) + fclose(meminfo); + if (cpuinfo) + fclose(cpuinfo); +} diff --git a/src/core-modules/linux/battery.cc b/src/core-modules/linux/battery.cc new file mode 100644 index 00000000..eab5aa8c --- /dev/null +++ b/src/core-modules/linux/battery.cc @@ -0,0 +1,102 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <unistd.h> + +#include <string> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "util.hpp" + +static std::string read_strip_syspath(const std::string_view path) +{ + std::string str = read_by_syspath(path); + debug("str = {} || path = {}", str, path); + + // optimization + if (str.back() == '\n') + str.pop_back(); + else if (str != UNKNOWN) + strip(str); + + return str; +} + +static std::string get_battery_info(const std::string& file) +{ + if (access("/sys/class/power_supply/", F_OK) != 0) + return MAGIC_LINE; + + for (const auto& dir_entry : std::filesystem::directory_iterator{ "/sys/class/power_supply/" }) + { + const std::string& path = dir_entry.path().string() + "/"; + debug("battery path = {}", path); + + const std::string& tmp = read_strip_syspath(path + "type"); + if (tmp == UNKNOWN || tmp != "Battery") + continue; + if (read_strip_syspath(path + "scope") == "Device") + continue; + + debug("battery found yeappyy"); + return read_strip_syspath(path + file); + } + + return UNKNOWN; +} + +// clang-format off +MODFUNC(battery_modelname) +{ return get_battery_info("model_name"); } + +MODFUNC(battery_perc) +{ return get_battery_info("capacity"); } + +MODFUNC(battery_status) +{ return get_battery_info("status"); } + +MODFUNC(battery_capacity_level) +{ return get_battery_info("capacity_level"); } + +MODFUNC(battery_technology) +{ return get_battery_info("technology"); } + +MODFUNC(battery_vendor) +{ return get_battery_info("manufacturer"); } + +double battery_temp() +{ + const std::string& temp = get_battery_info("temp"); + if (temp != UNKNOWN || temp != MAGIC_LINE) + return std::stod(temp) / 10; + + return 0; +} + +#endif diff --git a/src/query/linux/cpu.cpp b/src/core-modules/linux/cpu.cc similarity index 69% rename from src/query/linux/cpu.cpp rename to src/core-modules/linux/cpu.cc index fec73c9d..8abcd80e 100644 --- a/src/query/linux/cpu.cpp +++ b/src/core-modules/linux/cpu.cc @@ -1,46 +1,17 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - #include "platform.hpp" #if CF_ANDROID || CF_LINUX +#include <unistd.h> -#include <sys/types.h> - -#include <array> -#include <cstdlib> -#include <filesystem> -#include <fstream> +#include <cstring> #include <string> -#include <string_view> +#include "core-modules.hh" #include "fmt/format.h" -#include "query.hpp" +#include "libcufetch/common.hh" #include "switch_fnv1a.hpp" #include "util.hpp" -using namespace Query; +const std::string freq_dir = "/sys/devices/system/cpu/cpu0/cpufreq"; #if CF_ANDROID // https://en.wikipedia.org/wiki/List_of_Qualcomm_Snapdragon_systems_on_chips @@ -490,148 +461,63 @@ static std::string detect_mediatek(const std::string& model_name) } #endif // CF_ANDROID -static std::string get_from_text(std::string& line) +static void trim(char* str) { - std::string amount = line.substr(line.find(':') + 1); - strip(amount); - return amount; + if (!str) + return; + + // Trim leading space + char* p = str; + while (isspace((unsigned char)*p)) + ++p; + memmove(str, p, strlen(p) + 1); + + // Trim trailing space + p = str + strlen(str) - 1; + while (p >= str && isspace((unsigned char)*p)) + --p; + p[1] = '\0'; } -static CPU::CPU_t get_cpu_infos() +static bool read_value(const char* name, size_t n, bool do_rewind, char* buf, size_t buf_size) { - CPU::CPU_t ret; - debug("calling in CPU {}", __PRETTY_FUNCTION__); - constexpr std::string_view cpuinfo_path = "/proc/cpuinfo"; - std::ifstream file(cpuinfo_path.data()); - if (!file.is_open()) - { - error(_("Could not open {}"), cpuinfo_path); - return ret; - } - - std::string line; - float cpu_mhz = -1; - while (std::getline(file, line)) - { - if (hasStart(line, "model name")) - ret.name = get_from_text(line); - - else if (hasStart(line, "processor")) - ret.nproc = get_from_text(line); - - else if (hasStart(line, "cpu MHz")) - { - double tmp = std::stof(get_from_text(line)); - if (tmp > cpu_mhz) - cpu_mhz = tmp; - } - } - -#if CF_ANDROID - if (ret.name == UNKNOWN) + if (!cpuinfo || !buf || !buf_size) + return false; + if (do_rewind) + rewind(cpuinfo); + + char* line = NULL; + size_t len = 0; + bool found = false; + while (getline(&line, &len, cpuinfo) != -1) { - ret.modelname = get_android_property("ro.soc.model"); - if (ret.modelname.empty()) - { - ret.vendor = "MTK"; - ret.modelname = get_android_property("ro.mediatek.platform"); - } - if (ret.vendor.empty()) - { - ret.vendor = get_android_property("ro.soc.manufacturer"); - if (ret.vendor.empty()) - ret.vendor = get_android_property("ro.product.product.manufacturer"); - } - - if ((ret.vendor == "QTI" || ret.vendor == "QUALCOMM") && - (hasStart(ret.modelname, "SM") || hasStart(ret.modelname, "APQ") || hasStart(ret.modelname, "MSM") || - hasStart(ret.modelname, "SDM") || hasStart(ret.modelname, "QM"))) - ret.name = fmt::format("Qualcomm {} [{}]", detect_qualcomm(ret.modelname), ret.modelname); - else if (ret.vendor == "Samsung") - ret.name = fmt::format("Samsung {} [{}]", detect_exynos(ret.modelname), ret.modelname); - else if (ret.vendor == "MTK") - ret.name = fmt::format("Mediatek {} [{}]", detect_mediatek(ret.modelname), ret.modelname); - else - ret.name = ret.vendor + " " + ret.modelname; - } -#endif + if (strncmp(line, name, n)) + continue; - // sometimes /proc/cpuinfo at model name - // the name will contain the min freq - // happens on intel cpus especially - const size_t pos = ret.name.rfind('@'); - if (pos != std::string::npos) - ret.name.erase(pos - 1); + char* colon = strchr(line, ':'); + if (!colon) + continue; - cpu_mhz /= 1000; - ret.freq_max_cpuinfo = cpu_mhz; + // Extract and trim value + char* val = colon + 1; + while (isspace((unsigned char)*val)) + ++val; + trim(val); - // add 1 to the nproc - ret.nproc = fmt::to_string(std::stoi(ret.nproc) + 1); + // Safe copy to buffer + strncpy(buf, val, buf_size - 1); + buf[buf_size - 1] = '\0'; - const std::string freq_dir = "/sys/devices/system/cpu/cpu0/cpufreq"; - if (std::filesystem::exists(freq_dir)) - { - std::ifstream cpu_bios_limit_f(freq_dir + "/bios_limit"); - std::ifstream cpu_scaling_cur_f(freq_dir + "/scaling_cur_freq"); - std::ifstream cpu_scaling_max_f(freq_dir + "/scaling_max_freq"); - std::ifstream cpu_scaling_min_f(freq_dir + "/scaling_min_freq"); - - std::string freq_bios_limit, freq_cpu_scaling_cur, freq_cpu_scaling_max, freq_cpu_scaling_min; - - std::getline(cpu_bios_limit_f, freq_bios_limit); - std::getline(cpu_scaling_cur_f, freq_cpu_scaling_cur); - std::getline(cpu_scaling_max_f, freq_cpu_scaling_max); - std::getline(cpu_scaling_min_f, freq_cpu_scaling_min); - - ret.freq_bios_limit = freq_bios_limit.empty() ? 0 : (std::stof(freq_bios_limit) / 1000000); - ret.freq_cur = freq_cpu_scaling_cur.empty() ? 0 : (std::stof(freq_cpu_scaling_cur) / 1000000); - ret.freq_max = freq_cpu_scaling_max.empty() ? 0 : (std::stof(freq_cpu_scaling_max) / 1000000); - ret.freq_min = freq_cpu_scaling_min.empty() ? 0 : (std::stof(freq_cpu_scaling_min) / 1000000); + found = true; + break; } - return ret; + free(line); + return found; } -static double get_cpu_temp() +float cpu_temp() { -#if CF_ANDROID - // https://github.com/kamgurgul/cpu-info/blob/master/shared/src/androidMain/kotlin/com/kgurgul/cpuinfo/data/provider/TemperatureProvider.android.kt#L119 - constexpr std::array<std::string_view, 20> temp_paths = { - "/sys/devices/system/cpu/cpu0/cpufreq/cpu_temp", - "/sys/devices/system/cpu/cpu0/cpufreq/FakeShmoo_cpu_temp", - "/sys/class/thermal/thermal_zone0/temp", - "/sys/class/i2c-adapter/i2c-4/4-004c/temperature", - "/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/temperature", - "/sys/devices/platform/omap/omap_temp_sensor.0/temperature", - "/sys/devices/platform/tegra_tmon/temp1_input", - "/sys/kernel/debug/tegra_thermal/temp_tj", - "/sys/devices/platform/s5p-tmu/temperature", - "/sys/class/thermal/thermal_zone1/temp", - "/sys/class/hwmon/hwmon0/device/temp1_input", - "/sys/devices/virtual/thermal/thermal_zone1/temp", - "/sys/devices/virtual/thermal/thermal_zone0/temp", - "/sys/class/thermal/thermal_zone3/temp", - "/sys/class/thermal/thermal_zone4/temp", - "/sys/class/hwmon/hwmonX/temp1_input", - "/sys/devices/platform/s5p-tmu/curr_temp", - "/sys/htc/cpu_temp", - "/sys/devices/platform/tegra-i2c.3/i2c-4/4-004c/ext_temperature", - "/sys/devices/platform/tegra-tsensor/tsensor_temperature", - }; - for (const std::string_view path : temp_paths) - { - debug("checking {}", path); - if (!std::filesystem::exists(path)) - continue; - - const double ret = std::stod(read_by_syspath(path)) / 1000.0; - debug("cpu temp ret = {}", ret); - - if (ret >= -1.0 && ret <= 250.0) - return ret; - } -#else for (const auto& dir : std::filesystem::directory_iterator{ "/sys/class/hwmon/" }) { const std::string& name = read_by_syspath((dir.path() / "name").string()); @@ -639,64 +525,115 @@ static double get_cpu_temp() if (name != "cpu" && name != "k10temp" && name != "coretemp") continue; - const std::string& temp_file = std::filesystem::exists(dir.path() / "temp1_input") - ? dir.path() / "temp1_input" - : dir.path() / "device/temp1_input"; - if (!std::filesystem::exists(temp_file)) + const std::string& temp_file = (access((dir.path() / "temp1_input").string().c_str(), F_OK) != 0) + ? dir.path() / "device/temp1_input" + : dir.path() / "temp1_input"; + if (access(temp_file.c_str(), F_OK) != 0) continue; - const double ret = std::stod(read_by_syspath(temp_file)); + const float ret = std::stof(read_by_syspath(temp_file)); debug("cpu temp ret = {}", ret); - return ret / 1000.0; + return ret / 1000.0f; } -#endif + return 0.0f; +} - return 0.0; +#if CF_ANDROID +MODFUNC(android_cpu_model_name) +{ return get_android_property("ro.soc.model"); } + +MODFUNC(android_cpu_vendor) +{ + if (android_cpu_model_name(nullptr).empty()) + return "MTK"; + + std::string vendor = get_android_property("ro.soc.manufacturer"); + if (vendor.empty()) + vendor = get_android_property("ro.product.product.manufacturer"); + + return vendor; } +#endif -CPU::CPU() noexcept +MODFUNC(cpu_name) { - CHECK_INIT(!m_bInit) + char name[4096]; +#if CF_LINUX + if (!read_value("model name", "model name"_len, true, name, sizeof(name))) + return UNKNOWN; +#elif CF_ANDROID + if (!read_value("model name", "model name"_len, true, name, sizeof(name))) { - m_cpu_infos = get_cpu_infos(); - m_bInit = true; + const std::string& vendor = android_cpu_vendor(nullptr); + const std::string& model_name = android_cpu_model_name(nullptr); + if (vendor == "QTI" || vendor == "QUALCOMM") + strcpy(name, fmt::format("Qualcomm {} [{}]", detect_qualcomm(model_name), model_name).c_str()); + else if (vendor == "Samsung") + strcpy(name, fmt::format("Samsung {} [{}]", detect_exynos(model_name), model_name).c_str()); + else if (vendor == "MTK") + strcpy(name, fmt::format("Mediatek {} [{}]", detect_mediatek(model_name), model_name).c_str()); + else + strcpy(name, (vendor + " " + model_name).c_str()); } +#endif + // sometimes /proc/cpuinfo at model name + // the name will contain the min freq + // happens on intel cpus especially + char* at = strrchr(name, '@'); + if (!at) + return name; + if (at > name && *(at - 1) == ' ') + *(at - 1) = '\0'; + else + *at = '\0'; + + trim(name); + return name; } -// clang-format off -std::string& CPU::name() noexcept -{ return m_cpu_infos.name; } - -std::string& CPU::nproc() noexcept -{ return m_cpu_infos.nproc; } - -std::string& CPU::vendor() noexcept -{ return m_cpu_infos.vendor; } - -std::string& CPU::modelname() noexcept -{ return m_cpu_infos.modelname; } - -double& CPU::freq_bios_limit() noexcept -{ return m_cpu_infos.freq_bios_limit; } +MODFUNC(cpu_nproc) +{ + uint nproc = 0; + rewind(cpuinfo); -double& CPU::freq_cur() noexcept -{ return m_cpu_infos.freq_cur; } + char* line = NULL; + size_t len = 0; + while (getline(&line, &len, cpuinfo) != -1) + { + if (strncmp(line, "processor", "processor"_len) == 0) + nproc++; + } + free(line); + return fmt::to_string(nproc); +} -double& CPU::freq_max() noexcept -{ return (m_cpu_infos.freq_max <= 0) ? m_cpu_infos.freq_max_cpuinfo : m_cpu_infos.freq_max; } +MODFUNC(cpu_freq_cur) +{ + if (access((freq_dir + "/scaling_cur_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_cur_freq")) / 1000000); +} -double& CPU::freq_min() noexcept -{ return m_cpu_infos.freq_min; } +MODFUNC(cpu_freq_max) +{ + if (access((freq_dir + "/scaling_max_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_max_freq")) / 1000000); +} -double& CPU::temp() noexcept +MODFUNC(cpu_freq_min) { - static bool done = false; - if (!done) - m_cpu_infos.temp = get_cpu_temp(); - done = true; + if (access((freq_dir + "/scaling_min_freq").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/scaling_min_freq")) / 1000000); +} - return m_cpu_infos.temp; +MODFUNC(cpu_freq_bios) +{ + if (access((freq_dir + "/bios_limit").c_str(), F_OK) != 0) + return "0"; + return fmt::format("{:.2f}", std::stof(read_by_syspath(freq_dir + "/bios_limit")) / 1000000); } -#endif // CF_ANDROID || CF_LINUX +#endif diff --git a/src/core-modules/linux/disk.cc b/src/core-modules/linux/disk.cc new file mode 100644 index 00000000..765e0c3c --- /dev/null +++ b/src/core-modules/linux/disk.cc @@ -0,0 +1,290 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_ANDROID + +#include <mntent.h> +#include <sys/stat.h> +#include <sys/statvfs.h> +#include <unistd.h> + +#include <cstdio> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "libcufetch/config.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/disk/disk_linux.c +static bool is_physical_device(const mntent* device) +{ +#if !CF_ANDROID // On Android, `/dev` is not accessible, so that the following checks always fail + + // Always show the root path + if (strcmp(device->mnt_dir, "/") == 0) + return true; + + if (strcmp(device->mnt_fsname, "none") == 0) + return false; + + // DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem. + if (strcmp(device->mnt_type, "9p") == 0) + return std::string_view(device->mnt_opts).find("aname=drvfs") != std::string_view::npos; + + // ZFS pool + if (strcmp(device->mnt_type, "zfs") == 0) + return true; + + // Pseudo filesystems don't have a device in /dev + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + // #731 + if (strcmp(device->mnt_type, "bcachefs") == 0) + return true; + + if (hasStart(device->mnt_fsname + 5, "loop") || // Ignore loop devices + hasStart(device->mnt_fsname + 5, "ram") || // Ignore ram devices + hasStart(device->mnt_fsname + 5, "fd") // Ignore fd devices + ) + return false; + + struct stat deviceStat; + if (stat(device->mnt_fsname, &deviceStat) != 0) + return false; + + // Ignore all devices that are not block devices + if (!S_ISBLK(deviceStat.st_mode)) + return false; + +#else + + // Pseudo filesystems don't have a device in /dev + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + if (hasStart(device->mnt_fsname + 5, "loop") || // Ignore loop devices + hasStart(device->mnt_fsname + 5, "ram") || // Ignore ram devices + hasStart(device->mnt_fsname + 5, "fd") // Ignore fd devices + ) + return false; + + // https://source.android.com/docs/core/ota/apex?hl=zh-cn + if (hasStart(device->mnt_dir, "/apex/")) + return false; + +#endif // !CF_ANDROID + + return true; +} + +static bool is_removable(const mntent* device) +{ + if (!hasStart(device->mnt_fsname, "/dev/")) + return false; + + // like device->mnt_fsname.substr(5); + std::string sys_block_partition{ fmt::format("/sys/class/block/{}", (device->mnt_fsname + "/dev/"_len)) }; + // check if it's like /dev/sda1 + if (sys_block_partition.back() >= '0' && sys_block_partition.back() <= '9') + sys_block_partition.pop_back(); + + return read_by_syspath(sys_block_partition + "/removable") == "1"; +} + +static int get_disk_type(const mntent* device) +{ +#if CF_LINUX + int ret = 0; + + if (hasStart(device->mnt_dir, "/boot") || hasStart(device->mnt_dir, "/efi")) + ret = DISK_VOLUME_TYPE_HIDDEN; + else if (is_removable(device)) + ret = DISK_VOLUME_TYPE_EXTERNAL; + else + ret = DISK_VOLUME_TYPE_REGULAR; + + if (hasmntopt(device, MNTOPT_RO)) + ret |= DISK_VOLUME_TYPE_READ_ONLY; + + return ret; +#else // CF_ANDROID + if (strcmp(device->mnt_dir, "/") == 0 || strcmp(device->mnt_dir, "/storage/emulated") == 0) + return DISK_VOLUME_TYPE_REGULAR; + + if (hasStart(device->mnt_dir, "/mnt/media_rw/")) + return DISK_VOLUME_TYPE_EXTERNAL; + + return DISK_VOLUME_TYPE_HIDDEN; +#endif +} + +static std::string format_auto_query_string(std::string str, const struct mntent* device) +{ + replace_str(str, "%1", device->mnt_dir); + replace_str(str, "%2", device->mnt_fsname); + replace_str(str, "%3", device->mnt_type); + + replace_str(str, "%4", fmt::format("$<disk({}).total>", device->mnt_dir)); + replace_str(str, "%5", fmt::format("$<disk({}).free>", device->mnt_dir)); + replace_str(str, "%6", fmt::format("$<disk({}).used>", device->mnt_dir)); + replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", device->mnt_dir)); + replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", device->mnt_dir)); + + return str; +} + +static struct mntent* get_disk_info(const callbackInfo_t* callbackInfo) +{ + if (callbackInfo->moduleArgs->name != "disk" || + (callbackInfo->moduleArgs->name == "disk" && callbackInfo->moduleArgs->value.empty())) + die("Module disk doesn't have an argmument to the path/device to query"); + + const std::string& path = callbackInfo->moduleArgs->value; + if (access(path.c_str(), F_OK) != 0 || !mountsFile) + die("Failed to query disk at path: '{}", path); + + struct mntent* pDevice; + while ((pDevice = getmntent(mountsFile))) + { + debug("pDevice->mnt_dir = {:<50} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); + if (path == pDevice->mnt_dir || path == pDevice->mnt_fsname) + break; + } + + rewind(mountsFile); + return pDevice; +} + +static bool get_disk_usage_info(const callbackInfo_t* callbackInfo, struct statvfs* fs) +{ + struct mntent* pDevice = get_disk_info(callbackInfo); + const std::string& path = callbackInfo->moduleArgs->value; + const std::string& statpath = (hasStart(path, "/dev") && pDevice) ? pDevice->mnt_dir : path; + + return (statvfs(statpath.c_str(), fs) == 0); +} + +// clang-format off +// don't get confused by the name pls +MODFUNC(disk_fsname) +{ return get_disk_info(callbackInfo)->mnt_type; } + +MODFUNC(disk_device) +{ return get_disk_info(callbackInfo)->mnt_fsname; } + +MODFUNC(disk_mountdir) +{ return get_disk_info(callbackInfo)->mnt_dir; } + +// clang-format on +MODFUNC(disk_types) +{ + const int types = get_disk_type(get_disk_info(callbackInfo)); + std::string str; + if (types & DISK_VOLUME_TYPE_EXTERNAL) + str += "External, "; + if (types & DISK_VOLUME_TYPE_HIDDEN) + str += "Hidden, "; + if (types & DISK_VOLUME_TYPE_READ_ONLY) + str += "Read-only, "; + + if (!str.empty()) + str.erase(str.length() - 2); + + return str; +} + +MODFUNC(auto_disk) +{ + static std::vector<std::string> queried_devices; + const ConfigBase& config = callbackInfo->parse_args.config; + const std::string& auto_disks_fmt = config.getValue<std::string>("auto.disk.fmt", "${auto}Disk (%1): $<disk(%1)>"); + int auto_disks_types = 0; + for (const std::string& str : + config.getValueArrayStr("auto.disk.display-types", { "external", "regular", "read-only" })) + { + switch (fnv1a16::hash(str)) + { + case "removable"_fnv1a16: // deprecated + case "external"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_EXTERNAL; break; + case "regular"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_REGULAR; break; + case "read-only"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_READ_ONLY; break; + case "hidden"_fnv1a16: auto_disks_types |= DISK_VOLUME_TYPE_HIDDEN; break; + } + } + + long old_position = ftell(mountsFile); + if (old_position == -1L) + die("Failed to get initial file position"); + + struct mntent* pDevice; + while ((pDevice = getmntent(mountsFile))) + { + if (!is_physical_device(pDevice)) + continue; + + if (!(auto_disks_types & get_disk_type(pDevice))) + continue; + + old_position = ftell(mountsFile); + if (old_position == -1L) + break; + + debug("AUTO: pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); + callbackInfo->parse_args.no_more_reset = false; + callbackInfo->parse_args.tmp_layout.push_back( + parse(format_auto_query_string(auto_disks_fmt, pDevice), callbackInfo->parse_args)); + if (fseek(mountsFile, old_position, SEEK_SET) == -1) + die("Failed to seek back to saved position"); + } + return ""; +} + +double disk_total(const callbackInfo_t* callbackInfo) +{ + struct statvfs fs; + if (!get_disk_usage_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_blocks * fs.f_frsize); +} + +double disk_free(const callbackInfo_t* callbackInfo) +{ + struct statvfs fs; + if (!get_disk_usage_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_bfree * fs.f_frsize); +} + +double disk_used(const callbackInfo_t *callbackInfo) +{ + return disk_total(callbackInfo) - disk_free(callbackInfo); +} + +#endif diff --git a/src/core-modules/linux/gpu.cc b/src/core-modules/linux/gpu.cc new file mode 100644 index 00000000..e3aee59d --- /dev/null +++ b/src/core-modules/linux/gpu.cc @@ -0,0 +1,93 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <cstdint> +#include <filesystem> +#include <string> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "util.hpp" + +static std::string get_name(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) +{ + const std::string& name = binarySearchPCIArray(m_vendor_id_s, m_device_id_s); + debug("GPU binarySearchPCIArray name = {}", name); + const size_t first_bracket = name.find('['); + const size_t last_bracket = name.rfind(']'); + + // remove the chips name "TU106 [GeForce GTX 1650]" + // This should work for AMD and Intel too. + if (first_bracket != std::string::npos && last_bracket != std::string::npos) + return name.substr(first_bracket + 1, last_bracket - first_bracket - 1); + + return name; +} + +static std::string get_vendor(const std::string_view m_vendor_id_s) +{ return binarySearchPCIArray(m_vendor_id_s); } + +static std::string get_gpu_syspath(const std::string& id) +{ + const std::uint16_t max_iter = 10; + std::uint16_t id_iter = std::stoi(id); + std::string sys_path; + int i = 0; + for (; i <= max_iter; i++) + { + sys_path = "/sys/class/drm/card" + fmt::to_string(id_iter); + if (std::filesystem::exists(sys_path + "/device/device") && + std::filesystem::exists(sys_path + "/device/vendor")) + return sys_path; + else + id_iter++; + } + + error(_("Failed to parse GPU infos on the path /sys/class/drm/")); + return UNKNOWN; +} + +MODFUNC(gpu_name) +{ + const std::string& id = (callbackInfo && callbackInfo->moduleArgs->name.length() > 3) + ? callbackInfo->moduleArgs->name.substr(3) + : "0"; + const std::string& sys_path = get_gpu_syspath(id); + return get_name(read_by_syspath(sys_path + "/device/vendor"), read_by_syspath(sys_path + "/device/device")); +} + +MODFUNC(gpu_vendor) +{ + const std::string& id = (callbackInfo && callbackInfo->moduleArgs->name.length() > 3) + ? callbackInfo->moduleArgs->name.substr(3) + : "0"; + const std::string& sys_path = get_gpu_syspath(id); + return get_vendor(read_by_syspath(sys_path + "/device/vendor")); +} + +#endif diff --git a/src/core-modules/linux/os.cc b/src/core-modules/linux/os.cc new file mode 100644 index 00000000..65854a9f --- /dev/null +++ b/src/core-modules/linux/os.cc @@ -0,0 +1,155 @@ +#include "platform.hpp" +#if CF_LINUX + +#include <cassert> +#include <cstdio> +#include <cstring> +#include <fstream> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static std::string read_value(const std::string_view name) +{ + if (!os_release) + return UNKNOWN; + + rewind(os_release); + + std::string result{ UNKNOWN }; + char* line = nullptr; + size_t len = 0; + + while (getline(&line, &len, os_release) != -1) + { + if (name.length() > len || strncmp(line, name.data(), name.length()) != 0) + continue; + + char* start = strchr(line + name.length(), '"'); /* Get first occurence of " */ + if (start) + start++; /* Get after the " */ + else + start = line + name.length(); /* No ", get the start. */ + + char* end = strrchr(start, '"'); /* Get last occurence of " */ + if (!end) + end = line + strlen(line) - 1; /* Set to the end of the string -- no newline. */ + + result.assign(start, end - start); + break; + } + + free(line); + return result; +} + +unsigned long os_uptime() +{ + const std::string& buf = read_by_syspath("/proc/uptime"); + if (buf != UNKNOWN) + return std::stoul(buf.substr(0, buf.find('.'))); // 19065.18 190952.06 + + struct std::timespec uptime; + if (clock_gettime(CLOCK_BOOTTIME, &uptime) != 0) + return 0; + + return (unsigned long)uptime.tv_sec * 1000 + (unsigned long)uptime.tv_nsec / 1000000; +} + +MODFUNC(os_name) +{ return read_value("NAME="); } + +MODFUNC(os_pretty_name) +{ return read_value("PRETTY_NAME="); } + +MODFUNC(os_name_id) +{ return read_value("ID="); } + +MODFUNC(os_version_id) +{ return read_value("VERSION_ID="); } + +MODFUNC(os_version_codename) +{ return read_value("VERSION_CODENAME="); } + +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } + +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } + +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ + // there's no way PID 1 doesn't exist. + // This will always succeed (because we are on linux) + std::ifstream f_initsys("/proc/1/comm", std::ios::binary); + if (!f_initsys.is_open()) + die(_("/proc/1/comm doesn't exist! (what?)")); + + std::string initsys; + std::getline(f_initsys, initsys); + size_t pos = 0; + + if ((pos = initsys.find('\0')) != std::string::npos) + initsys.erase(pos); + + if ((pos = initsys.rfind('/')) != std::string::npos) + initsys.erase(0, pos + 1); + + return initsys; +} + +MODFUNC(os_initsys_version) +{ + std::string os_initsys_version; + std::string path; + char buf[PATH_MAX]; + if (realpath(which("init").c_str(), buf)) + path = buf; + + std::ifstream f(path, std::ios::in); + std::string line; + + const std::string& name = str_tolower(os_initsys_name(nullptr)); + switch (fnv1a16::hash(name)) + { + case "systemd"_fnv1a16: + case "systemctl"_fnv1a16: + { + while (read_binary_file(f, line)) + { + if (hasEnding(line, "running in %ssystem mode (%s)")) + { + os_initsys_version = line.substr("systemd "_len); + os_initsys_version.erase(os_initsys_version.find(' ')); + break; + } + } + } + break; + case "openrc"_fnv1a16: + { + std::string tmp; + while (read_binary_file(f, line)) + { + if (line == "RC_VERSION") + { + os_initsys_version = tmp; + break; + } + tmp = line; + } + } + break; + } + + return os_initsys_version; +} + +#endif diff --git a/src/core-modules/linux/ram.cc b/src/core-modules/linux/ram.cc new file mode 100644 index 00000000..27f8873e --- /dev/null +++ b/src/core-modules/linux/ram.cc @@ -0,0 +1,89 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_ANDROID + +#include <cstdio> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +static double read_value(const std::string_view key) +{ + if (!meminfo) + return 0.0; + + std::string result{ UNKNOWN }; + char* line = nullptr; + size_t len = 0; + + while (getline(&line, &len, meminfo) != -1) + { + if (strncmp(line, key.data(), key.length()) != 0) + continue; + + // Skip colon and whitespace + char* value = line + key.length(); + while (isspace(*value)) + value++; + + // Find end of numeric value (stop at first non-digit or '.') + char* end = value; + while (*end && (isdigit(*end) || *end == '.')) + end++; + + if (value != end) + result.assign(value, end - value); + break; + } + + free(line); + rewind(meminfo); + return std::stod(result) * 1024.0f; +} + +// clang-format off +double ram_free() +{ return read_value("MemAvailable:"); } + +double ram_total() +{ return read_value("MemTotal:"); } + +double ram_used() +{ return ram_total() - ram_free(); } + +double swap_free() +{ return read_value("SwapFree:"); } + +double swap_total() +{ return read_value("SwapTotal:"); } + +double swap_used() +{ return swap_total() - swap_free(); } + +#endif diff --git a/src/core-modules/linux/system.cc b/src/core-modules/linux/system.cc new file mode 100644 index 00000000..adf73019 --- /dev/null +++ b/src/core-modules/linux/system.cc @@ -0,0 +1,121 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <sys/utsname.h> + +#include <filesystem> +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "util.hpp" + +MODFUNC(host) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + std::string board_name{ UNKNOWN }, board_version{ UNKNOWN }, board_vendor{ UNKNOWN }; + + if (std::filesystem::exists(syspath + "/board_name")) + { + board_name = read_by_syspath(syspath + "/board_name"); + board_version = read_by_syspath(syspath + "/board_version"); + board_vendor = read_by_syspath(syspath + "/board_vendor"); + + if (board_vendor == "Micro-Star International Co., Ltd.") + board_vendor = "MSI"; + } + else if (std::filesystem::exists(syspath + "/product_name")) + { + board_name = read_by_syspath(syspath + "/product_name"); + + static constexpr std::string_view standard_pc_name = "Standard PC"; + if (board_name.substr(0, standard_pc_name.size()) == standard_pc_name) + { + // everyone does it like "KVM/QEMU Standard PC (...) (host_version)" so why not + board_vendor = "KVM/QEMU"; + board_version = std::string_view('(' + read_by_syspath(syspath + "/product_version") + ')').data(); + } + else + board_version = read_by_syspath(syspath + "/product_version"); + } + + return board_vendor + " " + board_name + " " + board_version; +} + +MODFUNC(host_name) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + if (std::filesystem::exists(syspath + "/board_name")) + return read_by_syspath(syspath + "/board_name"); + else if (std::filesystem::exists(syspath + "/product_name")) + return read_by_syspath(syspath + "/product_name"); + + return UNKNOWN; +} + +MODFUNC(host_version) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + if (std::filesystem::exists(syspath + "/board_name")) + return read_by_syspath(syspath + "/board_version"); + else if (std::filesystem::exists(syspath + "/product_name")) + return read_by_syspath(syspath + "/product_version"); + + return UNKNOWN; +} + +MODFUNC(host_vendor) +{ + const std::string syspath = "/sys/devices/virtual/dmi/id"; + + std::string board_vendor{ UNKNOWN }; + + if (std::filesystem::exists(syspath + "/board_name")) + { + board_vendor = read_by_syspath(syspath + "/board_vendor"); + } + else if (std::filesystem::exists(syspath + "/product_name")) + { + const std::string& board_name = read_by_syspath(syspath + "/product_name"); + if (hasStart(board_name, "Standard PC")) + board_vendor = "KVM/QEMU"; + } + + return board_vendor; +} + +MODFUNC(arch) +{ + return g_uname_infos.machine; +} + +#endif diff --git a/src/core-modules/linux/theme.cc b/src/core-modules/linux/theme.cc new file mode 100644 index 00000000..de6eb50d --- /dev/null +++ b/src/core-modules/linux/theme.cc @@ -0,0 +1,559 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX + +#include <algorithm> +#include <array> +#include <cstdint> +#include <fstream> +#include <functional> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "rapidxml-1.13/rapidxml.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +#if USE_DCONF +#include <client/dconf-client.h> +#include <glib/gvariant.h> +#endif + +using ThemeInfo = std::array<std::string, 3>; // [theme, icon_theme, font] +using CursorInfo = std::array<std::string, 2>; // [name, size] + +enum +{ + THEME_NAME, + THEME_ICON, + THEME_FONT +}; + +enum +{ + CURSOR_NAME, + CURSOR_SIZE +}; + +const std::string& configDir = getHomeConfigDir(); +const std::string gsetting_interface = (user_de_name(NULL) == "cinnamon") ? "org.cinnamon.desktop.interface" + : (de_name == "mate") ? "org.mate.interface" + : "org.gnome.desktop.interface"; + +const std::string dconf_interface = (user_de_name(NULL) == "cinnamon") ? "/org/cinnamon/desktop/interface" + : (de_name == "mate") ? "/org/mate/interface" + : "/org/gnome/desktop/interface"; + +static std::string get_xsettings_xfce4(const std::string_view property, const std::string_view subproperty) +{ + static bool done = false; + static rapidxml::xml_document<> doc; + static std::string buffer; + + if (!done) + { + const std::string& path = configDir + "/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml"; + std::ifstream f(path, std::ios::in); + if (!f.is_open()) + return MAGIC_LINE; + + buffer.assign(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>()); + buffer.push_back('\0'); + + doc.parse<0>(&buffer[0]); + done = true; + } + + rapidxml::xml_node<>* node1 = doc.first_node("channel")->first_node("property"); + for (; node1 && std::string_view(node1->first_attribute("name")->value()) != property; + node1 = node1->next_sibling("property")) + ; + + rapidxml::xml_node<>* node2 = node1->first_node("property"); + for (; node2; node2 = node2->next_sibling()) + { + if (std::string_view(node2->first_attribute("name")->value()) == subproperty && node2->first_attribute("value")) + return node2->first_attribute("value")->value(); + } + + return MAGIC_LINE; +} + +static std::string get_auto_gtk_format(const std::string_view gtk2, const std::string_view gtk3, + const std::string_view gtk4) +{ + if ((gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE)) + { + if (gtk2 == gtk3 && gtk2 == gtk4) + return fmt::format("{} [GTK2/3/4]", gtk4); + else if (gtk2 == gtk3) + return fmt::format("{} [GTK2/3], {} [GTK4]", gtk2, gtk4); + else if (gtk4 == gtk3) + return fmt::format("{} [GTK2], {} [GTK3/4]", gtk2, gtk4); + else + return fmt::format("{} [GTK2], {} [GTK3], {} [GTK4]", gtk2, gtk3, gtk4); + } + + else if (gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE) + { + if (gtk3 == gtk4) + return fmt::format("{} [GTK3/4]", gtk4); + else + return fmt::format("{} [GTK3], {} [GTK4]", gtk3, gtk4); + } + + else if (gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE) + { + if (gtk2 == gtk3) + return fmt::format("{} [GTK2/3]", gtk3); + else + return fmt::format("{} [GTK2], {} [GTK3]", gtk2, gtk3); + } + + else if (gtk4 != MAGIC_LINE) + return fmt::format("{} [GTK4]", gtk4); + else if (gtk3 != MAGIC_LINE) + return fmt::format("{} [GTK3]", gtk3); + else if (gtk2 != MAGIC_LINE) + return fmt::format("{} [GTK2]", gtk2); + + return MAGIC_LINE; +} + +// +// +// 1. Cursor +// +static CursorInfo get_cursor_xresources() +{ + std::string cursor_name{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; + const std::string& path = expandVar("~/.Xresources"); + std::ifstream f(path, std::ios::in); + if (!f.is_open()) + return { cursor_name, cursor_size }; + + std::uint16_t iter_index = 0; + std::string line; + while (std::getline(f, line) && iter_index < 2) + { + if (hasStart(line, "Xcursor.theme:")) + { + getFileValue(iter_index, line, cursor_name, "Xcursor.theme:"_len); + strip(cursor_name); + } + + else if (hasStart(line, "Xcursor.size:")) + { + getFileValue(iter_index, line, cursor_size, "Xcursor.size:"_len); + strip(cursor_size); + } + } + + return { cursor_name, cursor_size }; +} + +static CursorInfo get_cursor_dconf() +{ + std::string cursor{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; +#if USE_DCONF + void* handle = LOAD_LIBRARY("libdconf.so"); + if (!handle) + return { MAGIC_LINE, MAGIC_LINE }; + + LOAD_LIB_SYMBOL(handle, DConfClient*, dconf_client_new, void); + LOAD_LIB_SYMBOL(handle, GVariant*, dconf_client_read, DConfClient*, const char*); + LOAD_LIB_SYMBOL(handle, const gchar*, g_variant_get_string, GVariant*, gsize*); + LOAD_LIB_SYMBOL(handle, gint32, g_variant_get_int32, GVariant*); + + debug("calling {}", __PRETTY_FUNCTION__); + DConfClient* client = dconf_client_new(); + GVariant* variant; + + variant = dconf_client_read(client, (dconf_interface + "cursor-theme").c_str()); + if (variant) + cursor = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "cursor-size").c_str()); + if (variant) + cursor_size = fmt::to_string(g_variant_get_int32(variant)); +#endif + return { cursor, cursor_size }; +} + +static CursorInfo get_cursor_gsettings() +{ + const CursorInfo& dconf = get_cursor_dconf(); + if (dconf != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + return dconf; + + std::string cursor; + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "cursor-theme" }, cursor); + cursor.erase(std::remove(cursor.begin(), cursor.end(), '\''), cursor.end()); + + std::string cursor_size; + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "cursor-size" }, cursor_size); + cursor_size.erase(std::remove(cursor_size.begin(), cursor_size.end(), '\''), cursor_size.end()); + + if (cursor.empty()) + cursor = MAGIC_LINE; + if (cursor_size.empty()) + cursor_size = MAGIC_LINE; + return { cursor, cursor_size }; +} + +static CursorInfo get_gtk_cursor_config(const std::string_view path) +{ + std::string cursor{ MAGIC_LINE }, cursor_size{ MAGIC_LINE }; + std::ifstream f(path.data(), std::ios::in); + if (!f.is_open()) + return { cursor, cursor_size }; + + std::string line; + std::uint16_t iter_index = 0; + while (std::getline(f, line) && iter_index < 2) + { + if (hasStart(line, "gtk-cursor-theme-name=")) + getFileValue(iter_index, line, cursor, "gtk-cursor-theme-name="_len); + + else if (hasStart(line, "gtk-cursor-theme-size=")) + getFileValue(iter_index, line, cursor_size, "gtk-cursor-theme-size="_len); + } + + return { cursor, cursor_size }; +} + +static CursorInfo get_cursor_from_gtk_configs(const std::uint8_t ver) +{ + const std::array<std::string, 6> paths = { fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), + fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), + fmt::format("{}/gtkrc-{}.0", configDir, ver), + fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver) }; + + for (const std::string& path : paths) + { + const CursorInfo& result = get_gtk_cursor_config(path); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + return result; + } + return { MAGIC_LINE, MAGIC_LINE }; +} + +static CursorInfo get_de_cursor(const std::string_view de_name) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + { + debug("getting info on xfce4"); + return { get_xsettings_xfce4("Gtk", "CursorThemeName"), get_xsettings_xfce4("Gtk", "CursorThemeSize") }; + } + } + return { MAGIC_LINE, MAGIC_LINE }; +} + +// +// +// 2. GTK theme +// +static ThemeInfo get_gtk_theme_config(const std::string_view path) +{ + std::string theme{ MAGIC_LINE }, icon_theme{ MAGIC_LINE }, font{ MAGIC_LINE }; + std::ifstream f(path.data(), std::ios::in); + if (!f.is_open()) + return { theme, icon_theme, font }; + + std::string line; + std::uint16_t iter_index = 0; + while (std::getline(f, line) && iter_index < 3) + { + if (hasStart(line, "gtk-theme-name=")) + getFileValue(iter_index, line, theme, "gtk-theme-name="_len); + + else if (hasStart(line, "gtk-icon-theme-name=")) + getFileValue(iter_index, line, icon_theme, "gtk-icon-theme-name="_len); + + else if (hasStart(line, "gtk-font-name=")) + getFileValue(iter_index, line, font, "gtk-font-name="_len); + } + + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_dconf() +{ + std::string theme{ MAGIC_LINE }, icon_theme{ MAGIC_LINE }, font{ MAGIC_LINE }; +#if USE_DCONF + void* handle = LOAD_LIBRARY("libdconf.so"); + if (!handle) + return { theme, icon_theme, font }; + + LOAD_LIB_SYMBOL(handle, DConfClient*, dconf_client_new, void); + LOAD_LIB_SYMBOL(handle, GVariant*, dconf_client_read, DConfClient * client, const char*); + LOAD_LIB_SYMBOL(handle, const gchar*, g_variant_get_string, GVariant* value, gsize* lenght); + + debug("calling {}", __PRETTY_FUNCTION__); + DConfClient* client = dconf_client_new(); + GVariant* variant; + + variant = dconf_client_read(client, (dconf_interface + "gtk-theme").c_str()); + if (variant) + theme = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "icon-theme").c_str()); + if (variant) + icon_theme = g_variant_get_string(variant, NULL); + + variant = dconf_client_read(client, (dconf_interface + "font-name").c_str()); + if (variant) + font = g_variant_get_string(variant, NULL); + +#endif + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_gsettings() +{ + const ThemeInfo& dconf = get_gtk_theme_dconf(); + if (dconf != ThemeInfo{ MAGIC_LINE, MAGIC_LINE, MAGIC_LINE }) + return dconf; + + std::string theme, icon_theme, font; + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "gtk-theme" }, theme); + theme.erase(std::remove(theme.begin(), theme.end(), '\''), theme.end()); + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "icon-theme" }, icon_theme); + icon_theme.erase(std::remove(icon_theme.begin(), icon_theme.end(), '\''), icon_theme.end()); + + read_exec({ "gsettings", "get", gsetting_interface.c_str(), "font-name" }, font); + font.erase(std::remove(font.begin(), font.end(), '\''), font.end()); + + if (theme.empty()) + theme = MAGIC_LINE; + if (icon_theme.empty()) + icon_theme = MAGIC_LINE; + if (font.empty()) + font = MAGIC_LINE; + return { theme, icon_theme, font }; +} + +static ThemeInfo get_gtk_theme_from_configs(const std::uint8_t ver) +{ + const std::array<std::string, 6> paths = { fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), + fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), + fmt::format("{}/gtkrc-{}.0", configDir, ver), + fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), + fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver) }; + + for (const auto& path : paths) + { + const ThemeInfo& result = get_gtk_theme_config(path); + if (result != ThemeInfo{ MAGIC_LINE, MAGIC_LINE, MAGIC_LINE }) + return result; + } + return get_gtk_theme_gsettings(); +} + +static ThemeInfo get_de_gtk_theme(const std::string_view de_name, const std::uint8_t ver) +{ + switch (fnv1a16::hash(str_tolower(de_name.data()))) + { + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: + { + debug("getting info on xfce4"); + return { get_xsettings_xfce4("Net", "ThemeName"), get_xsettings_xfce4("Net", "IconThemeName"), + get_xsettings_xfce4("Gtk", "FontName") }; + } + } + return get_gtk_theme_from_configs(ver); +} + +const std::string& wmde_name = + (de_name != MAGIC_LINE && de_name == wm_name) || de_name == MAGIC_LINE ? wm_name : de_name; + +MODFUNC(theme_gtk_name) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_NAME]; +} + +MODFUNC(theme_gtk_icon) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_ICON]; +} + +MODFUNC(theme_gtk_font) +{ + const moduleArgs_t* moduleArg = callbackInfo->moduleArgs; + for (; moduleArg && moduleArg->name != "gtk"; moduleArg = moduleArg->next) + ; + if (!moduleArg) + die("GTK version not provided"); + int ver = std::stoi(moduleArg->value); + + const ThemeInfo& result = is_tty ? get_gtk_theme_from_configs(ver) : get_de_gtk_theme(wmde_name, ver); + + return result[THEME_FONT]; +} + +MODFUNC(theme_gtk_all_name) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_NAME], result_gtk3[THEME_NAME], result_gtk4[THEME_NAME]); +} + +MODFUNC(theme_gtk_all_icon) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_ICON], result_gtk3[THEME_ICON], result_gtk4[THEME_ICON]); +} + +MODFUNC(theme_gtk_all_font) +{ + const ThemeInfo& result_gtk2 = is_tty ? get_gtk_theme_from_configs(2) : get_de_gtk_theme(wmde_name, 2); + const ThemeInfo& result_gtk3 = is_tty ? get_gtk_theme_from_configs(3) : get_de_gtk_theme(wmde_name, 3); + const ThemeInfo& result_gtk4 = is_tty ? get_gtk_theme_from_configs(4) : get_de_gtk_theme(wmde_name, 4); + + return get_auto_gtk_format(result_gtk2[THEME_FONT], result_gtk3[THEME_FONT], result_gtk4[THEME_FONT]); +} + +MODFUNC(theme_gsettings_name) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_NAME]; +} + +MODFUNC(theme_gsettings_icon) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_ICON]; +} + +MODFUNC(theme_gsettings_font) +{ + const ThemeInfo& result = get_gtk_theme_gsettings(); + return result[THEME_FONT]; +} + +MODFUNC(theme_gsettings_cursor_name) +{ + const CursorInfo& result = get_cursor_gsettings(); + return result[CURSOR_NAME]; +} + +MODFUNC(theme_gsettings_cursor_size) +{ + const CursorInfo& result = get_cursor_gsettings(); + return result[CURSOR_SIZE]; +} + +const std::array<std::function<CursorInfo()>, 6> funcs{ + std::function<CursorInfo()>{ []() { return get_de_cursor(wmde_name); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(4); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(3); } }, + std::function<CursorInfo()>{ []() { return get_cursor_from_gtk_configs(2); } }, + std::function<CursorInfo()>{ []() { return get_cursor_xresources(); } }, + std::function<CursorInfo()>{ []() { return get_cursor_gsettings(); } } +}; + +MODFUNC(theme_cursor_name) +{ + CursorInfo result; + + for (const auto& method : funcs) + { + result = method(); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + break; + } + + if (result[CURSOR_NAME] == MAGIC_LINE) + return MAGIC_LINE; + + std::string& cursor_name = result[CURSOR_NAME]; + size_t pos = 0; + if ((pos = cursor_name.rfind("cursor")) != std::string::npos) + cursor_name.erase(pos); + if ((pos = cursor_name.rfind('_')) != std::string::npos) + cursor_name.erase(pos - 1); + + return cursor_name; +} + +MODFUNC(theme_cursor_size) +{ + CursorInfo result; + + for (const auto& method : funcs) + { + result = method(); + if (result != CursorInfo{ MAGIC_LINE, MAGIC_LINE }) + break; + } + + if (result[CURSOR_SIZE] == MAGIC_LINE) + return MAGIC_LINE; + + std::string& cursor_size = result[CURSOR_SIZE]; + size_t pos = 0; + if ((pos = cursor_size.rfind("cursor")) != std::string::npos) + cursor_size.erase(pos); + if ((pos = cursor_size.rfind('_')) != std::string::npos) + cursor_size.erase(pos - 1); + + return cursor_size; +} + +#endif // CF_LINUX diff --git a/src/core-modules/linux/user.cc b/src/core-modules/linux/user.cc new file mode 100644 index 00000000..3163efcc --- /dev/null +++ b/src/core-modules/linux/user.cc @@ -0,0 +1,464 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_LINUX || CF_MACOS + +#include <unistd.h> + +#include <fstream> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "tiny-process-library/process.hpp" +#include "util.hpp" +#include "utils/dewm.hh" +#include "utils/term.hh" + +#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) +#include <sys/socket.h> +#include <wayland-client.h> +#endif + +using namespace TinyProcessLib; + +// clang-format off +static std::string get_term_name_env(bool get_default = false) +{ + if (getenv("SSH_TTY") != NULL) + return getenv("SSH_TTY"); + + if (getenv("KITTY_PID") != NULL || + getenv("KITTY_INSTALLATION_DIR") != NULL || + getenv("KITTY_PUBLIC_KEY") != NULL || + getenv("KITTY_WINDOW_ID") != NULL) + return "kitty"; + + if (getenv("ALACRITTY_SOCKET") != NULL || + getenv("ALACRITTY_LOG") != NULL || + getenv("ALACRITTY_WINDOW_ID") != NULL) + return "alacritty"; + + if (getenv("TERMUX_VERSION") != NULL || + getenv("TERMUX_MAIN_PACKAGE_FORMAT") != NULL) + return "com.termux"; + + if(getenv("KONSOLE_VERSION") != NULL) + return "konsole"; + + if (getenv("GNOME_TERMINAL_SCREEN") != NULL || + getenv("GNOME_TERMINAL_SERVICE") != NULL) + return "gnome-terminal"; + + if (get_default) + { + char *env = getenv("TERM_PROGRAM"); + if (env != NULL) + { + if (hasStart(env, "Apple")) + return "Apple Terminal"; + + return env; + } + + env = getenv("TERM"); + if (env != NULL) + return env; + } + + return UNKNOWN; +} + +MODFUNC(user_name) +{ return g_pwd->pw_name; } + +MODFUNC(user_shell_path) +{ return g_pwd->pw_shell; } + +// clang-format on +MODFUNC(user_shell_name) +{ + return user_shell_path(callbackInfo).substr(user_shell_path(callbackInfo).rfind('/') + 1); +} + +MODFUNC(user_shell_version) +{ + const std::string& shell_name = user_shell_name(callbackInfo); + std::string ret; + + if (shell_name == "nu") + Process("nu -c \"version | get version\"", "", [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + else + Process(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data())), "", + [&](const char* bytes, size_t n) { ret.assign(bytes, n); }); + + strip(ret); + return ret; +} + +std::string get_terminal_pid() +{ + // customfetch -> shell -> terminal + const pid_t ppid = getppid(); + std::ifstream ppid_f(fmt::format("/proc/{}/status", ppid), std::ios::in); + std::string line, term_pid{ "0" }; + while (std::getline(ppid_f, line)) + { + if (hasStart(line, "PPid:")) + { + term_pid = line.substr("PPid:"_len); + strip(term_pid); + break; + } + } + debug("term_pid = {}", term_pid); + + if (std::stoi(term_pid) < 1) + return MAGIC_LINE; + + return term_pid; +} + +std::string get_terminal_name() +{ + if (term_pid == MAGIC_LINE) + return get_term_name_env(true); + + std::ifstream f("/proc/" + term_pid + "/comm", std::ios::in); + std::string term_name; + if (f.is_open()) + std::getline(f, term_name); + else + term_name = get_term_name_env(true); + + return term_name; +} + +MODFUNC(user_term_name) +{ + if (is_tty) + return term_name; + + // st (suckless terminal) + if (term_name == "exe") + term_name = "st"; + // either gnome-console or "gnome-terminal-" + // I hope this is not super stupid + else if (hasStart(term_name, "gnome-console")) + term_name.erase("gnome-console"_len + 1); + else if (hasStart(term_name, "gnome-terminal")) + term_name.erase("gnome-terminal"_len + 1); + + const std::string& osname = os_name(callbackInfo); + // let's try to get the real terminal name + // on NixOS, instead of returning the -wrapped name. + // tested on gnome-console, kitty, st and alacritty + // hope now NixOS users will know the terminal they got, along the version if possible + if (osname.find("NixOS") != osname.npos || (hasEnding(term_name, "wrapped") && which("nix") != UNKNOWN)) + { + // /nix/store/sha256string-gnome-console-0.31.0/bin/.kgx-wrapped + char buf[PATH_MAX]; + std::string tmp_name = realpath(("/proc/" + term_pid + "/exe").c_str(), buf); + + size_t pos; + if ((pos = tmp_name.find('-')) != std::string::npos) + tmp_name.erase(0, pos + 1); // gnome-console-0.31.0/bin/.kgx-wrapped + + if ((pos = tmp_name.find('/')) != std::string::npos) + tmp_name.erase(pos); // gnome-console-0.31.0 + + if ((pos = tmp_name.rfind('-')) != std::string::npos) + tmp_name.erase(pos); // gnome-console EZ + + term_name = tmp_name; + } + + // sometimes may happen that the terminal name from /comm + // at the end has some letfover characters from /cmdline + if (!std::isalnum(term_name.back())) + { + size_t i = term_name.size(); + while (i > 0) + { + char ch = term_name[i - 1]; + // stop when we find an a num or alpha char + // example with "gnome-terminal-" + if (std::isalnum(static_cast<unsigned char>(ch))) + break; + term_name.erase(--i, 1); + } + } + + return term_name; +} + +MODFUNC(user_term_version) +{ + if (is_tty) + return ""; + + const std::string& term_name = user_term_name(callbackInfo); + if (term_name.empty()) + return UNKNOWN; + + bool remove_term_name = true; + std::string ret; + + switch (fnv1a16::hash(str_tolower(term_name.data()))) + { + case "st"_fnv1a16: + if (fast_detect_st_ver(ret)) + remove_term_name = false; + break; + + case "konsole"_fnv1a16: + if (fast_detect_konsole_ver(ret)) + remove_term_name = false; + break; + + case "xterm"_fnv1a16: get_term_version_exec(term_name, ret, true); break; + + default: get_term_version_exec(term_name, ret); + } + + debug("get_term_version ret = {}", ret); + + if (ret.empty()) + return UNKNOWN; + + if (hasStart(ret, "# GNOME")) + { + if (hasStart(ret, "# GNOME Console ")) + ret.erase(0, "# GNOME Console"_len); + else if (hasStart(ret, "# GNOME Terminal ")) + ret.erase(0, "# GNOME Terminal "_len); + debug("gnome ret = {}", ret); + remove_term_name = false; + } + // Xterm(388) + else if (term_name == "xterm") + { + ret.erase(0, term_name.length() + 1); // 388) + ret.pop_back(); // 388 + return ret; + } + + if (remove_term_name) + ret.erase(0, term_name.length() + 1); + + const size_t pos = ret.find(' '); + if (pos != std::string::npos) + ret.erase(pos); + + debug("get_term_version ret after = {}", ret); + return ret; +} + +std::string get_wm_name(std::string& wm_path_exec) +{ + std::string path, proc_name, wm_name; + const uid_t uid = getuid(); + +#if !CF_MACOS + for (auto const& dir_entry : std::filesystem::directory_iterator{ "/proc/" }) + { + if (!std::isdigit((dir_entry.path().string().at(6)))) // /proc/5 + continue; + + path = dir_entry.path() / "loginuid"; + std::ifstream f_uid(path, std::ios::binary); + std::string s_uid; + std::getline(f_uid, s_uid); + if (std::stoul(s_uid) != uid) + continue; + + path = dir_entry.path() / "cmdline"; + std::ifstream f_cmdline(path, std::ios::binary); + std::getline(f_cmdline, proc_name); + + size_t pos = 0; + if ((pos = proc_name.find('\0')) != std::string::npos) + proc_name.erase(pos); + + if ((pos = proc_name.rfind('/')) != std::string::npos) + proc_name.erase(0, pos + 1); + + debug("WM proc_name = {}", proc_name); + + if ((wm_name = prettify_wm_name(proc_name)) == MAGIC_LINE) + continue; + + char buf[PATH_MAX]; + if (realpath((dir_entry.path().string() + "/exe").c_str(), buf)) + wm_path_exec = buf; + else + wm_path_exec = UNKNOWN; + + break; + } +#endif + + debug("wm_name = {}", wm_name); + if (wm_name.empty()) + return MAGIC_LINE; + + return wm_name; +} + +static std::string get_wm_wayland_name(std::string& wm_path_exec) +{ +#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) + void* handle = LOAD_LIBRARY("libwayland-client.so"); + if (!handle) + return get_wm_name(wm_path_exec); + + LOAD_LIB_SYMBOL(handle, wl_display*, wl_display_connect, const char* name) + LOAD_LIB_SYMBOL(handle, void, wl_display_disconnect, wl_display* display) + LOAD_LIB_SYMBOL(handle, int, wl_display_get_fd, wl_display* display) + + std::string ret = MAGIC_LINE; + + struct wl_display* display = wl_display_connect(NULL); + + struct ucred ucred; + socklen_t len = sizeof(struct ucred); + if (getsockopt(wl_display_get_fd(display), SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) + return MAGIC_LINE; + + std::ifstream f(fmt::format("/proc/{}/comm", ucred.pid), std::ios::in); + f >> ret; + wl_display_disconnect(display); + + char buf[PATH_MAX]; + wm_path_exec = realpath(fmt::format("/proc/{}/exe", ucred.pid).c_str(), buf); + + UNLOAD_LIBRARY(handle) + + return prettify_wm_name(ret); +#else + return get_wm_name(wm_path_exec); +#endif +} + +MODFUNC(user_wm_name) +{ + if (!wm_name.empty()) + return wm_name; + if (is_tty) + return MAGIC_LINE; + + const char* env = std::getenv("WAYLAND_DISPLAY"); + if (env != nullptr && env[0] != '\0') + wm_name = get_wm_wayland_name(wm_path_exec); + else + wm_name = get_wm_name(wm_path_exec); + + if (de_name == wm_name) + de_name = MAGIC_LINE; + + return wm_name; +} + +MODFUNC(user_wm_version) +{ + if (is_tty) + return MAGIC_LINE; + user_wm_name(callbackInfo); // populate wm_path_exec if haven't already + std::string wm_version; + if (wm_name == "Xfwm4" && get_fast_xfwm4_version(wm_version, wm_path_exec)) + return wm_version; + + if (wm_name == "dwm") + read_exec({ wm_path_exec.c_str(), "-v" }, wm_version, true); + else + read_exec({ wm_path_exec.c_str(), "--version" }, wm_version); + + if (wm_name == "Xfwm4") + wm_version.erase(0, "\tThis is xfwm4 version "_len); // saying only "xfwm4 4.18.2 etc." no? + else + wm_version.erase(0, wm_name.length() + 1); + + const size_t pos = wm_version.find(' '); + if (pos != std::string::npos) + wm_version.erase(pos); + + return prettify_wm_name(wm_version); +} + +MODFUNC(user_de_name) +{ + if (is_tty || ((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && de_name == wm_name)) + { + de_name = MAGIC_LINE; + return de_name; + } + + de_name = parse_de_env(); + debug("get_de_name = {}", de_name); + if (hasStart(de_name, "X-")) + de_name.erase(0, 2); + + if (de_name == wm_name) + de_name = MAGIC_LINE; + + return de_name; +} + +MODFUNC(user_de_version) +{ + if (is_tty || de_name == UNKNOWN || de_name == MAGIC_LINE || de_name.empty()) + return UNKNOWN; + + switch (fnv1a16::hash(str_tolower(de_name))) + { + case "mate"_fnv1a16: return get_mate_version(); + case "cinnamon"_fnv1a16: return get_cinnamon_version(); + + case "kde"_fnv1a16: return get_kwin_version(); + + case "xfce"_fnv1a16: + case "xfce4"_fnv1a16: return get_xfce4_version(); + + case "gnome"_fnv1a16: + case "gnome-shell"_fnv1a16: + { + std::string ret; + read_exec({ "gnome-shell", "--version" }, ret); + ret.erase(0, ret.rfind(' ')); + return ret; + } + default: + { + std::string ret; + read_exec({ de_name.data(), "--version" }, ret); + ret.erase(0, ret.rfind(' ')); + return ret; + } + } +} + +#endif diff --git a/src/query/linux/utils/dewm.cpp b/src/core-modules/linux/utils/dewm.cc similarity index 92% rename from src/query/linux/utils/dewm.cpp rename to src/core-modules/linux/utils/dewm.cc index 41b7cc71..09eb2b93 100644 --- a/src/query/linux/utils/dewm.cpp +++ b/src/core-modules/linux/utils/dewm.cc @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -43,12 +43,12 @@ * SOFTWARE. */ - -#include "dewm.hpp" +#include "dewm.hh" #include <cstdlib> #include <fstream> +#include "libcufetch/common.hh" #include "rapidxml-1.13/rapidxml.hpp" #include "switch_fnv1a.hpp" #include "util.hpp" @@ -191,7 +191,7 @@ std::string get_mate_version() return ret; } - std::string buffer((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); + std::string buffer(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>{}); buffer.push_back('\0'); rapidxml::xml_document<> doc; @@ -277,10 +277,13 @@ std::string get_cinnamon_version() static std::string get_xfce4_version_lib() { - LOAD_LIBRARY("libxfce4util.so", return UNKNOWN) - LOAD_LIB_SYMBOL(const char*, xfce_version_string, void) + void* handle = LOAD_LIBRARY("libxfce4util.so"); + if (!handle) + return UNKNOWN; + + LOAD_LIB_SYMBOL(handle, const char*, xfce_version_string, void) const std::string& ret = xfce_version_string(); - UNLOAD_LIBRARY() + UNLOAD_LIBRARY(handle) return ret; } diff --git a/src/query/linux/utils/dewm.hpp b/src/core-modules/linux/utils/dewm.hh similarity index 71% rename from src/query/linux/utils/dewm.hpp rename to src/core-modules/linux/utils/dewm.hh index 50536c1d..3af8fa59 100644 --- a/src/query/linux/utils/dewm.hpp +++ b/src/core-modules/linux/utils/dewm.hh @@ -1,32 +1,31 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #ifndef _DEWM_HPP #define _DEWM_HPP - #include <string> #include <string_view> @@ -36,6 +35,6 @@ std::string get_mate_version(); std::string get_xfce4_version(); std::string get_cinnamon_version(); std::string get_kwin_version(); -bool get_fast_xfwm4_version(std::string& ret, const std::string& exec_path); +bool get_fast_xfwm4_version(std::string& ret, const std::string& exec_path); -#endif // _DEWM_HPP +#endif // _DEWM_HPP diff --git a/src/query/linux/utils/packages.cpp b/src/core-modules/linux/utils/packages.cc similarity index 86% rename from src/query/linux/utils/packages.cpp rename to src/core-modules/linux/utils/packages.cc index b1ba2067..a323fe32 100644 --- a/src/query/linux/utils/packages.cpp +++ b/src/core-modules/linux/utils/packages.cc @@ -1,29 +1,29 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#include "packages.hpp" +#include "packages.hh" #include <algorithm> #include <filesystem> @@ -31,6 +31,7 @@ #include <string> #include "switch_fnv1a.hpp" +#include "util.hpp" static size_t get_num_count_dir(const std::string_view path) { @@ -59,15 +60,15 @@ static size_t get_num_string_file(const std::string_view path, const std::string return ret; } +#define ADD_PKGS_COUNT(pkgman) \ + if (pkgs_count.pkgman > 0) \ + ret += fmt::format("{} ({}), ", pkgs_count.pkgman, #pkgman); + std::string get_all_pkgs(const Config& config) { std::string ret; pkgs_managers_count_t pkgs_count; -#define ADD_PKGS_COUNT(pkgman) \ - if (pkgs_count.pkgman > 0) \ - ret += fmt::format("{} ({}), ", pkgs_count.pkgman, #pkgman); - for (const std::string& name : config.pkgs_managers) { switch (fnv1a16::hash(name)) @@ -105,3 +106,5 @@ std::string get_all_pkgs(const Config& config) return ret; } + +#undef ADD_PKGS_COUNT diff --git a/src/query/linux/utils/packages.hpp b/src/core-modules/linux/utils/packages.hh similarity index 74% rename from src/query/linux/utils/packages.hpp rename to src/core-modules/linux/utils/packages.hh index f2331c19..8266f803 100644 --- a/src/query/linux/utils/packages.hpp +++ b/src/core-modules/linux/utils/packages.hh @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ diff --git a/src/query/linux/utils/term.cpp b/src/core-modules/linux/utils/term.cc similarity index 99% rename from src/query/linux/utils/term.cpp rename to src/core-modules/linux/utils/term.cc index 5b1e55b7..721b655a 100644 --- a/src/query/linux/utils/term.cpp +++ b/src/core-modules/linux/utils/term.cc @@ -18,7 +18,7 @@ * SOFTWARE. */ -#include "term.hpp" +#include "term.hh" #include <fstream> diff --git a/src/core-modules/linux/utils/term.hh b/src/core-modules/linux/utils/term.hh new file mode 100644 index 00000000..47397aee --- /dev/null +++ b/src/core-modules/linux/utils/term.hh @@ -0,0 +1,36 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef _TERM_HPP +#define _TERM_HPP + +#include <string> + +void get_term_version_exec(const std::string_view term, std::string& ret, bool _short = false, bool _stderr = false); + +bool fast_detect_konsole_ver(std::string& ret); +bool fast_detect_st_ver(std::string& ret); + +#endif // _TERM_HPP diff --git a/src/core-modules/macos/battery.cc b/src/core-modules/macos/battery.cc new file mode 100644 index 00000000..36775e26 --- /dev/null +++ b/src/core-modules/macos/battery.cc @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +// clang-format off +MODFUNC(battery_modelname) +{ return MAGIC_LINE; } + +MODFUNC(battery_vendor) +{ return MAGIC_LINE; } + +MODFUNC(battery_capacity_level) +{ return MAGIC_LINE; } + +MODFUNC(battery_technology) +{ return MAGIC_LINE; } + +MODFUNC(battery_status) +{ return MAGIC_LINE; } + +MODFUNC(battery_perc) +{ return MAGIC_LINE; } + +double battery_temp() +{ return 0; } + +#endif diff --git a/src/core-modules/macos/cpu.cc b/src/core-modules/macos/cpu.cc new file mode 100644 index 00000000..0bc25a5f --- /dev/null +++ b/src/core-modules/macos/cpu.cc @@ -0,0 +1,97 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/sysctl.h> +#include <unistd.h> + +#include <cstdint> +#include <ratio> +#include <string> + +#include "core-modules.hh" + +static bool get_sysctl(int name[2], void* ret, size_t* oldlenp) +{ + return (sysctl(name, 2, ret, oldlenp, NULL, 0) == 0); +} + +static bool get_sysctl(const char* name, void* ret, size_t* oldlenp) +{ + return (sysctlbyname(name, ret, oldlenp, NULL, 0) == 0); +} + +float cpu_temp() { return 0; } + +MODFUNC(cpu_name) +{ + char buf[1024]; + size_t len = sizeof(buf); + get_sysctl("machdep.cpu.brand_string", &buf, &len); + return buf; +} + +MODFUNC(cpu_nproc) +{ + char buf[1024]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.logicalcpu_max", &buf, &len)) + get_sysctl("hw.ncpu", &buf, &len); + return buf; +} + +MODFUNC(cpu_freq_cur) +{ + std::uint64_t freq = 0; + size_t length = sizeof(freq); + if (!get_sysctl("hw.cpufrequency", &freq, &length)) + get_sysctl((int[2]){ CTL_HW, HW_CPU_FREQ }, &freq, &length); + + return fmt::to_string(static_cast<double>(freq) / std::giga().num); +} + +MODFUNC(cpu_freq_min) +{ + std::uint64_t freq = 0; + size_t length = sizeof(freq); + get_sysctl("hw.cpufrequency_min", &freq, &length); + + return fmt::to_string(static_cast<double>(freq) / std::giga().num); +} + +MODFUNC(cpu_freq_max) +{ + std::uint64_t freq = 0; + size_t length = sizeof(freq); + get_sysctl("hw.cpufrequency_max", &freq, &length); + + return fmt::to_string(static_cast<double>(freq) / std::giga().num); +} + +MODFUNC(cpu_freq_bios) { return MAGIC_LINE; } + +#endif diff --git a/src/core-modules/macos/disk.cc b/src/core-modules/macos/disk.cc new file mode 100644 index 00000000..1a3efaf7 --- /dev/null +++ b/src/core-modules/macos/disk.cc @@ -0,0 +1,155 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/types.h> + +#include <string> + +#include "core-modules.hh" +#include "fmt/format.h" +#include "libcufetch/common.hh" +#include "util.hpp" + +static std::string format_auto_query_string(std::string str, const struct statfs* fs) +{ + replace_str(str, "%1", fs->f_mntonname); + replace_str(str, "%2", fs->f_mntfromname); + replace_str(str, "%3", fs->f_fstypename); + + replace_str(str, "%4", fmt::format("$<disk({}).total>", fs->f_mntonname)); + replace_str(str, "%5", fmt::format("$<disk({}).free>", fs->f_mntonname)); + replace_str(str, "%6", fmt::format("$<disk({}).used>", fs->f_mntonname)); + replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", fs->f_mntonname)); + replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", fs->f_mntonname)); + + return str; +} + +static int get_disk_type(const int flags) +{ + int type = 0; + if (flags & MNT_DONTBROWSE) + type = DISK_VOLUME_TYPE_HIDDEN; + else if (flags & MNT_REMOVABLE || !(flags & MNT_LOCAL)) + type = DISK_VOLUME_TYPE_EXTERNAL; + else + type = DISK_VOLUME_TYPE_REGULAR; + + if (flags & MNT_RDONLY) + type |= DISK_VOLUME_TYPE_READ_ONLY; + + return type; +} + +static bool get_disk_info(const callbackInfo_t* callbackInfo, struct statfs* fs) +{ + if (callbackInfo->moduleArgs->name != "disk" || + (callbackInfo->moduleArgs->name == "disk" && callbackInfo->moduleArgs->value.empty())) + die("Module disk doesn't have an argmument to the path/device to query"); + + const std::string& path = callbackInfo->moduleArgs->value; + return (statfs(path.c_str(), fs) != 0); +} + +MODFUNC(disk_fsname) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_fstypename; +} + +MODFUNC(disk_device) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_mntfromname; +} + +MODFUNC(disk_mountdir) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + return fs.f_mntonname; +} + +MODFUNC(auto_disk) +{ return MAGIC_LINE; } + +MODFUNC(disk_types) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return MAGIC_LINE; + + const int types = get_disk_type(fs.f_flags); + std::string str; + if (types & DISK_VOLUME_TYPE_EXTERNAL) + str += "External, "; + if (types & DISK_VOLUME_TYPE_HIDDEN) + str += "Hidden, "; + if (types & DISK_VOLUME_TYPE_READ_ONLY) + str += "Read-only, "; + + if (!str.empty()) + str.erase(str.length() - 2); + + return str; +} + +double disk_total(const callbackInfo_t* callbackInfo) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_blocks * fs.f_bsize); +} + +double disk_free(const callbackInfo_t* callbackInfo) +{ + struct statfs fs; + if (!get_disk_info(callbackInfo, &fs)) + return 0; + + return static_cast<double>(fs.f_bfree * fs.f_bsize); +} + +double disk_used(const callbackInfo_t *callbackInfo) +{ + return disk_total(callbackInfo) - disk_free(callbackInfo); +} + +#endif diff --git a/src/core-modules/macos/gpu.cc b/src/core-modules/macos/gpu.cc new file mode 100644 index 00000000..54f40c18 --- /dev/null +++ b/src/core-modules/macos/gpu.cc @@ -0,0 +1,38 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +MODFUNC(gpu_name) +{ return MAGIC_LINE; } + +MODFUNC(gpu_vendor) +{ return "Intel"; } + +#endif diff --git a/src/core-modules/macos/os.cc b/src/core-modules/macos/os.cc new file mode 100644 index 00000000..1245027e --- /dev/null +++ b/src/core-modules/macos/os.cc @@ -0,0 +1,163 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/sysctl.h> + +#include <fstream> +#include <string> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "rapidxml-1.13/rapidxml.hpp" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +std::ifstream f("/System/Library/CoreServices/SystemVersion.plist", std::ios::in); +std::string buffer(std::istreambuf_iterator<char>{ f }, std::istreambuf_iterator<char>{}); +rapidxml::xml_document<> doc; + +static std::string get_codename(const std::string_view os_version_id) +{ + std::string major; + std::string minor{ UNKNOWN }; + size_t pos1 = os_version_id.find('.'); + if (pos1 != os_version_id.npos) + { + major = os_version_id.substr(0, pos1); + size_t pos2 = os_version_id.find('.', pos1 + 1); + if (pos2 != os_version_id.npos) + minor = os_version_id.substr(pos1 + 1, pos2); + } + + switch (fnv1a16::hash(major)) + { + case "15"_fnv1a16: return "Sequoia"; + case "14"_fnv1a16: return "Sonoma"; + case "13"_fnv1a16: return "Ventura"; + case "12"_fnv1a16: return "Monterey"; + case "11"_fnv1a16: return "Big Sur"; + case "10"_fnv1a16: + { + switch (fnv1a16::hash(minor)) + { + case "16"_fnv1a16: return "Big Sur"; + case "15"_fnv1a16: return "Catalina"; + case "14"_fnv1a16: return "Mojave"; + case "13"_fnv1a16: return "High Sierra"; + case "12"_fnv1a16: return "Sierra"; + case "11"_fnv1a16: return "El Capitan"; + case "10"_fnv1a16: return "Yosemite"; + case "9"_fnv1a16: return "Mavericks"; + case "8"_fnv1a16: return "Mountain Lion"; + case "7"_fnv1a16: return "Lion"; + case "6"_fnv1a16: return "Snow Leopard"; + case "5"_fnv1a16: return "Leopard"; + case "4"_fnv1a16: return "Tiger"; + case "3"_fnv1a16: return "Panther"; + case "2"_fnv1a16: return "Jaguar"; + case "1"_fnv1a16: return "Puma"; + case "0"_fnv1a16: return "Cheetah"; + } + } + } + + return UNKNOWN; +} + +static void assert_doc() +{ + if (!doc.first_node("plist")) + { + buffer.push_back('\0'); + doc.parse<0>(&buffer[0]); + } +} + +static std::string get_plist_value(const std::string_view name) +{ + assert_doc(); + rapidxml::xml_node<>* root_node = doc.first_node("plist")->first_node("dict")->first_node("key"); + for (; root_node; root_node = root_node->next_sibling()) + { + const std::string_view key = root_node->value(); // <key>ProductName</key> + root_node = root_node->next_sibling(); + const std::string_view value = root_node->value(); // <string>macOS</string> + if (key == name) + return value.data(); + } + return UNKNOWN; +} + +MODFUNC(os_pretty_name) +{ + const std::string& codename = os_version_codename(nullptr); + if (codename != UNKNOWN) + return os_name(nullptr) + " " + os_version_id(nullptr) + " (" + codename + ")"; + return os_name(nullptr) + " " + os_version_id(nullptr); +} + +unsigned long os_uptime() +{ + struct timeval boot_time; + size_t size = sizeof(boot_time); + int name[] = { CTL_KERN, KERN_BOOTTIME }; + if (sysctl(name, 2, &boot_time, &size, NULL, 0) != 0) + die(_("failed to get uptime")); + + return time(NULL) - boot_time.tv_sec; +} + +// clang-format off +MODFUNC(os_name) +{ return get_plist_value("ProductName"); } + +MODFUNC(os_name_id) +{ return "macos"; } + +MODFUNC(os_version_id) +{ return get_plist_value("ProductUserVisibleVersion"); } + +MODFUNC(os_version_codename) +{ return get_codename(os_version_id(nullptr)); } + +MODFUNC(os_kernel_name) +{ return g_uname_infos.sysname; } + +MODFUNC(os_kernel_version) +{ return g_uname_infos.release; } + +MODFUNC(os_hostname) +{ return g_uname_infos.nodename; } + +MODFUNC(os_initsys_name) +{ return MAGIC_LINE; } + +MODFUNC(os_initsys_version) +{ return UNKNOWN; } + +#endif diff --git a/src/core-modules/macos/ram.cc b/src/core-modules/macos/ram.cc new file mode 100644 index 00000000..24e8c4c7 --- /dev/null +++ b/src/core-modules/macos/ram.cc @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <mach/mach.h> +#include <sys/sysctl.h> +#include <unistd.h> + +#include <cstdint> + +#include "core-modules.hh" + +xsw_usage xsw; +size_t xsw_length{ sizeof(xsw) }; + +static bool populate_xsw() +{ + int name[2] = { CTL_VM, VM_SWAPUSAGE }; + return (sysctl(name, 2, &xsw, &xsw_length, NULL, 0) != 0); +} + +double ram_total() +{ + int name[2] = { CTL_HW, HW_MEMSIZE }; + uint64_t amount = 0; + size_t length = sizeof(amount); + if (sysctl(name, 2, &amount, &length, NULL, 0) != 0) + return 0.0; + return static_cast<double>(amount); +} + +double ram_used() +{ + int name[2] = { CTL_HW, HW_PAGESIZE }; + uint64_t amount = 0, page_size = 0; + size_t length = sizeof(amount); + mach_msg_type_number_t count = HOST_VM_INFO64_COUNT; + vm_statistics64_data_t vmstat; + + sysctl(name, 2, &page_size, &length, NULL, 0); + if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info64_t)(&vmstat), &count) != KERN_SUCCESS) + return 0.0; + + return static_cast<double>( + ((uint64_t) + + vmstat.active_count + + vmstat.inactive_count + + vmstat.speculative_count + + vmstat.wire_count + + vmstat.compressor_page_count + - vmstat.purgeable_count + - vmstat.external_page_count + ) * page_size); +} + +double ram_free() +{ return ram_total() - ram_used(); } + +double swap_used() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_used; +} + +double swap_free() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_avail; +} + +double swap_total() +{ + if (!populate_xsw()) + return 0.0; + return xsw.xsu_total; +} + +#endif diff --git a/src/core-modules/macos/system.cc b/src/core-modules/macos/system.cc new file mode 100644 index 00000000..c6f074c9 --- /dev/null +++ b/src/core-modules/macos/system.cc @@ -0,0 +1,287 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include <sys/sysctl.h> + +#include <regex> // -100000000 runtime/compile-time +#include <string> +#include <string_view> + +#include "core-modules.hh" +#include "libcufetch/common.hh" +#include "switch_fnv1a.hpp" +#include "util.hpp" + +static bool get_sysctl(const char* name, void* ret, size_t* oldlenp) +{ + return (sysctlbyname(name, ret, oldlenp, NULL, 0) == 0); +} + +// https://github.com/fastfetch-cli/fastfetch/blob/a734f18fd56014f5c0b9fb388727b778e2bc05d1/src/detection/host/host_mac.c#L4 +const std::string get_host_from_family(const std::string_view host_family) +{ + // Macbook Pro: https://support.apple.com/en-us/HT201300 + // Macbook Air: https://support.apple.com/en-us/HT201862 + // Mac mini: https://support.apple.com/en-us/HT201894 + // iMac: https://support.apple.com/en-us/HT201634 + // Mac Pro: https://support.apple.com/en-us/HT202888 + // Mac Studio: https://support.apple.com/en-us/HT213073 + + if (hasStart(host_family, "MacBookPro")) + { + const std::string_view version = host_family.substr("MacBookPro"_len); + switch (fnv1a16::hash(version.data())) + { + case "18,3"_fnv1a16: + case "18,4"_fnv1a16: return "MacBook Pro (14-inch, 2021)"; + case "18,1"_fnv1a16: + case "18,2"_fnv1a16: return "MacBook Pro (16-inch, 2021)"; + case "17,1"_fnv1a16: return "MacBook Pro (13-inch, M1, 2020)"; + case "16,3"_fnv1a16: return "MacBook Pro (13-inch, 2020, Two Thunderbolt 3 ports)"; + case "16,2"_fnv1a16: return "MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)"; + case "16,4"_fnv1a16: + case "16,1"_fnv1a16: return "MacBook Pro (16-inch, 2019)"; + case "15,4"_fnv1a16: return "MacBook Pro (13-inch, 2019, Two Thunderbolt 3 ports)"; + case "15,3"_fnv1a16: return "MacBook Pro (15-inch, 2019)"; + case "15,2"_fnv1a16: return "MacBook Pro (13-inch, 2018/2019, Four Thunderbolt 3 ports)"; + case "15,1"_fnv1a16: return "MacBook Pro (15-inch, 2018/2019)"; + case "14,3"_fnv1a16: return "MacBook Pro (15-inch, 2017)"; + case "14,2"_fnv1a16: return "MacBook Pro (13-inch, 2017, Four Thunderbolt 3 ports)"; + case "14,1"_fnv1a16: return "MacBook Pro (13-inch, 2017, Two Thunderbolt 3 ports)"; + case "13,3"_fnv1a16: return "MacBook Pro (15-inch, 2016)"; + case "13,2"_fnv1a16: return "MacBook Pro (13-inch, 2016, Four Thunderbolt 3 ports)"; + case "13,1"_fnv1a16: return "MacBook Pro (13-inch, 2016, Two Thunderbolt 3 ports)"; + case "12,1"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Early 2015)"; + case "11,4"_fnv1a16: + case "11,5"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Mid 2015)"; + case "11,2"_fnv1a16: + case "11,3"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Late 2013/Mid 2014)"; + case "11,1"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Late 2013/Mid 2014)"; + case "10,2"_fnv1a16: return "MacBook Pro (Retina, 13-inch, Late 2012/Early 2013)"; + case "10,1"_fnv1a16: return "MacBook Pro (Retina, 15-inch, Mid 2012/Early 2013)"; + case "9,2"_fnv1a16: return "MacBook Pro (13-inch, Mid 2012)"; + case "9,1"_fnv1a16: return "MacBook Pro (15-inch, Mid 2012)"; + case "8,3"_fnv1a16: return "MacBook Pro (17-inch, 2011)"; + case "8,2"_fnv1a16: return "MacBook Pro (15-inch, 2011)"; + case "8,1"_fnv1a16: return "MacBook Pro (13-inch, 2011)"; + case "7,1"_fnv1a16: return "MacBook Pro (13-inch, Mid 2010)"; + case "6,2"_fnv1a16: return "MacBook Pro (15-inch, Mid 2010)"; + case "6,1"_fnv1a16: return "MacBook Pro (17-inch, Mid 2010)"; + case "5,5"_fnv1a16: return "MacBook Pro (13-inch, Mid 2009)"; + case "5,3"_fnv1a16: return "MacBook Pro (15-inch, Mid 2009)"; + case "5,2"_fnv1a16: return "MacBook Pro (17-inch, Mid/Early 2009)"; + case "5,1"_fnv1a16: return "MacBook Pro (15-inch, Late 2008)"; + case "4,1"_fnv1a16: return "MacBook Pro (17/15-inch, Early 2008)"; + } + } + else if (hasStart(host_family, "MacBookAir")) + { + const std::string_view version = host_family.substr("MacBookAir"_len); + switch (fnv1a16::hash(version.data())) + { + case "10,1"_fnv1a16: return "MacBook Air (M1, 2020)"; + case "9,1"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2020)"; + case "8,2"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2019)"; + case "8,1"_fnv1a16: return "MacBook Air (Retina, 13-inch, 2018)"; + case "7,2"_fnv1a16: return "MacBook Air (13-inch, Early 2015/2017)"; + case "7,1"_fnv1a16: return "MacBook Air (11-inch, Early 2015)"; + case "6,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2013/Early 2014)"; + case "6,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2013/Early 2014)"; + case "5,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2012)"; + case "5,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2012)"; + case "4,2"_fnv1a16: return "MacBook Air (13-inch, Mid 2011)"; + case "4,1"_fnv1a16: return "MacBook Air (11-inch, Mid 2011)"; + case "3,2"_fnv1a16: return "MacBook Air (13-inch, Late 2010)"; + case "3,1"_fnv1a16: return "MacBook Air (11-inch, Late 2010)"; + case "2,1"_fnv1a16: return "MacBook Air (Mid 2009)"; + } + } + else if (hasStart(host_family, "Macmini")) + { + const std::string_view version = host_family.substr("Macmini"_len); + switch (fnv1a16::hash(version.data())) + { + case "9,1"_fnv1a16: return "Mac mini (M1, 2020)"; + case "8,1"_fnv1a16: return "Mac mini (2018)"; + case "7,1"_fnv1a16: return "Mac mini (Mid 2014)"; + case "6,1"_fnv1a16: + case "6,2"_fnv1a16: return "Mac mini (Late 2012)"; + case "5,1"_fnv1a16: + case "5,2"_fnv1a16: return "Mac mini (Mid 2011)"; + case "4,1"_fnv1a16: return "Mac mini (Mid 2010)"; + case "3,1"_fnv1a16: return "Mac mini (Early/Late 2009)"; + } + } + else if (hasStart(host_family, "MacBook")) + { + const std::string_view version = host_family.substr("MacBook"_len); + switch (fnv1a16::hash(version.data())) + { + case "10,1"_fnv1a16: return "MacBook (Retina, 12-inch, 2017)"; + case "9,1"_fnv1a16: return "MacBook (Retina, 12-inch, Early 2016)"; + case "8,1"_fnv1a16: return "MacBook (Retina, 12-inch, Early 2015)"; + case "7,1"_fnv1a16: return "MacBook (13-inch, Mid 2010)"; + case "6,1"_fnv1a16: return "MacBook (13-inch, Late 2009)"; + case "5,2"_fnv1a16: return "MacBook (13-inch, Early/Mid 2009)"; + } + } + else if (hasStart(host_family, "MacPro")) + { + const std::string_view version = host_family.substr("MacPro"_len); + switch (fnv1a16::hash(version.data())) + { + case "7,1"_fnv1a16: return "Mac Pro (2019)"; + case "6,1"_fnv1a16: return "Mac Pro (Late 2013)"; + case "5,1"_fnv1a16: return "Mac Pro (Mid 2010 - Mid 2012)"; + case "4,1"_fnv1a16: return "Mac Pro (Early 2009)"; + } + } + else if (hasStart(host_family, "Mac")) + { + const std::string_view version = host_family.substr("Mac"_len); + switch (fnv1a16::hash(version.data())) + { + case "16,13"_fnv1a16: return "MacBook Air (15-inch, M4, 2025)"; + case "16,12"_fnv1a16: return "MacBook Air (13-inch, M4, 2025)"; + case "16,11"_fnv1a16: + case "16,10"_fnv1a16: return "Mac Mini (2024)"; + case "16,9"_fnv1a16: return "Mac Studio (M4 Max, 2025)"; + case "16,3"_fnv1a16: return "iMac (24-inch, 2024, Four Thunderbolt / USB 4 ports)"; + case "16,2"_fnv1a16: return "iMac (24-inch, 2024, Two Thunderbolt / USB 4 ports)"; + case "16,1"_fnv1a16: return "MacBook Pro (14-inch, 2024, Three Thunderbolt 4 ports)"; + case "16,6"_fnv1a16: + case "16,8"_fnv1a16: return "MacBook Pro (14-inch, 2024, Three Thunderbolt 5 ports)"; + case "16,7"_fnv1a16: + case "16,5"_fnv1a16: return "MacBook Pro (16-inch, 2024, Three Thunderbolt 5 ports)"; + case "15,14"_fnv1a16: return "Mac Studio (M3 Ultra, 2025)"; + case "15,13"_fnv1a16: return "MacBook Air (15-inch, M3, 2024)"; + case "15,12"_fnv1a16: return "MacBook Air (13-inch, M3, 2024)"; + case "15,3"_fnv1a16: return "MacBook Pro (14-inch, Nov 2023, Two Thunderbolt / USB 4 ports)"; + case "15,4"_fnv1a16: return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports)"; + case "15,5"_fnv1a16: return "iMac (24-inch, 2023, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; + case "15,6"_fnv1a16: + case "15,8"_fnv1a16: + case "15,10"_fnv1a16: return "MacBook Pro (14-inch, Nov 2023, Three Thunderbolt 4 ports)"; + case "15,7"_fnv1a16: + case "15,9"_fnv1a16: + case "15,11"_fnv1a16: return "MacBook Pro (16-inch, Nov 2023, Three Thunderbolt 4 ports)"; + case "14,15"_fnv1a16: return "MacBook Air (15-inch, M2, 2023)"; + case "14,14"_fnv1a16: return "Mac Studio (M2 Ultra, 2023, Two Thunderbolt 4 front ports)"; + case "14,13"_fnv1a16: return "Mac Studio (M2 Max, 2023, Two USB-C front ports)"; + case "14,8"_fnv1a16: return "Mac Pro (2023)"; + case "14,6"_fnv1a16: + case "14,10"_fnv1a16: return "MacBook Pro (16-inch, 2023)"; + case "14,5"_fnv1a16: + case "14,9"_fnv1a16: return "MacBook Pro (14-inch, 2023)"; + case "14,3"_fnv1a16: return "Mac mini (M2, 2023, Two Thunderbolt 4 ports)"; + case "14,12"_fnv1a16: return "Mac mini (M2, 2023, Four Thunderbolt 4 ports)"; + case "14,7"_fnv1a16: return "MacBook Pro (13-inch, M2, 2022)"; + case "14,2"_fnv1a16: return "MacBook Air (M2, 2022)"; + case "13,1"_fnv1a16: return "Mac Studio (M1 Max, 2022, Two USB-C front ports)"; + case "13,2"_fnv1a16: return "Mac Studio (M1 Ultra, 2022, Two Thunderbolt 4 front ports)"; + } + } + else if (hasStart(host_family, "iMac")) + { + const std::string_view version = host_family.substr("iMac"_len); + switch (fnv1a16::hash(version.data())) + { + case "21,1"_fnv1a16: return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports, Two USB 3 ports)"; + case "21,2"_fnv1a16: return "iMac (24-inch, M1, 2021, Two Thunderbolt / USB 4 ports)"; + case "20,1"_fnv1a16: + case "20,2"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2020)"; + case "19,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2019)"; + case "19,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, 2019)"; + case "Pro1,1"_fnv1a16: return "iMac Pro (2017)"; + case "18,3"_fnv1a16: return "iMac (Retina 5K, 27-inch, 2017)"; + case "18,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, 2017)"; + case "18,1"_fnv1a16: return "iMac (21.5-inch, 2017)"; + case "17,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, Late 2015)"; + case "16,2"_fnv1a16: return "iMac (Retina 4K, 21.5-inch, Late 2015)"; + case "16,1"_fnv1a16: return "iMac (21.5-inch, Late 2015)"; + case "15,1"_fnv1a16: return "iMac (Retina 5K, 27-inch, Late 2014 - Mid 2015)"; + case "14,4"_fnv1a16: return "iMac (21.5-inch, Mid 2014)"; + case "14,2"_fnv1a16: return "iMac (27-inch, Late 2013)"; + case "14,1"_fnv1a16: return "iMac (21.5-inch, Late 2013)"; + case "13,2"_fnv1a16: return "iMac (27-inch, Late 2012)"; + case "13,1"_fnv1a16: return "iMac (21.5-inch, Late 2012)"; + case "12,2"_fnv1a16: return "iMac (27-inch, Mid 2011)"; + case "12,1"_fnv1a16: return "iMac (21.5-inch, Mid 2011)"; + case "11,3"_fnv1a16: return "iMac (27-inch, Mid 2010)"; + case "11,2"_fnv1a16: return "iMac (21.5-inch, Mid 2010)"; + case "10,1"_fnv1a16: return "iMac (27/21.5-inch, Late 2009)"; + case "9,1"_fnv1a16: return "iMac (24/20-inch, Early 2009)"; + } + } + + return UNKNOWN; +} + +MODFUNC(host_vendor) +{ return "Apple"; } + +MODFUNC(host_name) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return MAGIC_LINE; + + const std::string host = get_host_from_family(buf); + return host.substr(0, host.find('(') - 1); +} + +MODFUNC(host_version) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return UNKNOWN; + + const std::string& host = get_host_from_family(buf); + std::regex year_regex(R"(\b(19|20)\d{2}\b)"); // Matches 1900–2099 + std::smatch match; + if (std::regex_search(host, match, year_regex)) + return match.str(0); + return UNKNOWN; +} + +MODFUNC(host) +{ + char buf[4096]; + size_t len = sizeof(buf); + if (!get_sysctl("hw.model", buf, &len)) + return MAGIC_LINE; + + return get_host_from_family(buf); +} + +MODFUNC(arch) +{ return g_uname_infos.machine; } + +#endif diff --git a/src/core-modules/macos/theme.cc b/src/core-modules/macos/theme.cc new file mode 100644 index 00000000..1c137f8a --- /dev/null +++ b/src/core-modules/macos/theme.cc @@ -0,0 +1,46 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include "platform.hpp" +#if CF_MACOS + +#include "core-modules.hh" +#include "libcufetch/common.hh" + +MODFUNC(theme_gtk_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_font) { return MAGIC_LINE; } +MODFUNC(theme_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_cursor_size) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_name) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_icon) { return MAGIC_LINE; } +MODFUNC(theme_gtk_all_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_icon) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_font) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_name) { return MAGIC_LINE; } +MODFUNC(theme_gsettings_cursor_size) { return MAGIC_LINE; } + +#endif // CF_MACOS diff --git a/src/display.cpp b/src/display.cpp index 40117265..7f6ab76a 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -1,38 +1,49 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ // Implementation of the system behind displaying/rendering the information #include "display.hpp" + #include <cstddef> +#include <cstdio> +#include <string> + +#include "platform.hpp" #ifndef GUI_APP -# define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION #endif +#if CF_MACOS +#include <util.h> +#else #include <pty.h> +#endif + +#include <sys/ioctl.h> #include <termios.h> #include <unistd.h> @@ -43,76 +54,83 @@ #include <iostream> #include <vector> -#include "config.hpp" +#include "core-modules.hh" #include "fmt/core.h" #include "fmt/format.h" #include "parse.hpp" -#include "query.hpp" +#include "platform.hpp" #include "stb_image.h" +#include "tiny-process-library/process.hpp" #include "utf8/checked.h" #include "util.hpp" std::string Display::detect_distro(const Config& config) { - debug("/etc/os-release = \n{}", read_shell_exec("cat /etc/os-release 2> /dev/null")); + // debug("/etc/os-release = \n{}", read_shell_exec("cat /etc/os-release 2> /dev/null")); - if (!config.m_custom_distro.empty()) + if (!config.args_custom_distro.empty()) { - return fmt::format("{}/ascii/{}.txt", config.data_dir, config.m_custom_distro); + return fmt::format("{}/ascii/{}.txt", config.data_dir, config.args_custom_distro); } else { - Query::System system; - std::string format; - - format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(system.os_id())); + std::string format; + format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(os_name_id(nullptr))); + debug("checking path in {}: {}", __func__, format); if (std::filesystem::exists(format)) return format; - format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(system.os_name())); + format = fmt::format("{}/ascii/{}.txt", config.data_dir, str_tolower(os_name(nullptr))); + debug("checking path in {}: {}", __func__, format); if (std::filesystem::exists(format)) return format; } -#if ANDROID_APP +#if CF_ANDROID return config.data_dir + "/ascii/android.txt"; +#elif CF_MACOS + return config.data_dir + "/ascii/mac.txt"; #else return config.data_dir + "/ascii/linux.txt"; #endif } -#if !ANDROID_APP -static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std::vector<std::string>& layout, - const Config& config, const colors_t& colors, - const std::string_view path, const std::uint16_t font_width, - const std::uint16_t font_height) +static std::vector<std::string> render_with_image(const moduleMap_t& modulesInfo, std::vector<std::string>& layout, + const Config& config, const std::filesystem::path& path, + const std::uint16_t font_width, const std::uint16_t font_height) { int image_width, image_height, channels; // load the image and get its width and height - unsigned char* img = stbi_load(path.data(), &image_width, &image_height, &channels, 0); + unsigned char* img = stbi_load(path.c_str(), &image_width, &image_height, &channels, 0); if (!img) - die(_("Unable to load image '{}'"), path); + die(_("Unable to load image '{}'"), path.string()); stbi_image_free(img); + if (Display::ascii_logo_fd != -1) + { + remove(path.c_str()); + close(Display::ascii_logo_fd); + } - std::string _; + std::string _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, true }; + parse_args_t parse_args{ modulesInfo, _, layout, tmp_layout, config, true }; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; - if (!config.gui && !config.m_disable_colors) - { +#if !GUI_APP + if (!config.args_disable_colors) layout[i].insert(0, NOCOLOR); - } +#endif if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } @@ -126,16 +144,16 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: const size_t width = image_width / font_width; const size_t height = image_height / font_height; - if (config.m_image_backend == "kitty") - taur_exec({ "kitty", "+kitten", "icat", + if (config.args_image_backend == "kitty") + TinyProcessLib::Process({ "kitty", "+kitten", "icat", "--align", (config.logo_position == "top" ? "center" : config.logo_position), - "--place", fmt::format("{}x{}@0x0", width, height), path }); - else if (config.m_image_backend == "viu") - taur_exec({ "viu", "-t", "-w", fmt::to_string(width), "-h", fmt::to_string(height), path }); + "--place", fmt::format("{}x{}@0x0", width, height), path.string() }); + else if (config.args_image_backend == "viu") + TinyProcessLib::Process({ "viu", "-t", "-w", fmt::to_string(width), "-h", fmt::to_string(height), path.string() }); else die(_("The image backend '{}' isn't supported, only 'kitty' and 'viu'.\n" "Please currently use the GUI mode for rendering the image/gif (use -h for more details)"), - config.m_image_backend); + config.args_image_backend); if (config.logo_position == "top") { @@ -145,8 +163,13 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: return layout; } - for (auto& str : layout) - for (size_t _ = 0; _ < width + config.offset; ++_) + const unsigned int offset = + (config.offset.back() == '%') + ? Display::calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), width, 0) + : std::stoi(config.offset); + + for (std::string& str : layout) + for (size_t _ = 0; _ < width + offset; ++_) str.insert(0, " "); return layout; @@ -157,8 +180,8 @@ static std::vector<std::string> render_with_image(systemInfo_t& systemInfo, std: static bool get_pos(int& y, int& x) { std::array<char, 32> buf; - int ret, i, pow; - char ch; + int ret, i, pow; + char ch; y = 0; x = 0; @@ -198,31 +221,21 @@ static bool get_pos(int& y, int& x) tcsetattr(0, TCSANOW, &restore); return true; } -#endif -std::vector<std::string> Display::render(const Config& config, const colors_t& colors, const bool already_analyzed_file, - const std::string_view path) +std::vector<std::string> Display::render(const Config& config, const bool already_analyzed_file, + const std::filesystem::path& path, const moduleMap_t& moduleMap) { - systemInfo_t systemInfo{}; - std::vector<std::string> asciiArt{}, layout{ config.m_args_layout.empty() ? config.layout : config.m_args_layout }; + std::vector<std::string> asciiArt{}, layout{ config.args_layout.empty() ? config.layout : config.args_layout }; - debug("Display::render path = {}", path); - -#if ANDROID_APP - const std::string_view space = " "; -#else - const std::string_view space = " "; -#endif + debug("Display::render path = {}", path.string()); bool isImage = false; std::ifstream file; - std::ifstream fileToAnalyze; // both have same path - if (!config.m_disable_source) + if (!config.args_disable_source) { - file.open(path.data(), std::ios::binary); - fileToAnalyze.open(path.data(), std::ios::binary); - if (!file.is_open() || !fileToAnalyze.is_open()) - die(_("Could not open logo file '{}'"), path); + file.open(path.string(), std::ios::binary); + if (!file.is_open()) + die(_("Could not open logo file '{}'"), path.string()); // first check if the file is an image // without even using the same library that "file" uses @@ -231,8 +244,10 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c { debug("Display::render() analyzing file"); std::array<unsigned char, 32> buffer; - fileToAnalyze.read(reinterpret_cast<char*>(&buffer.at(0)), buffer.size()); + file.read(reinterpret_cast<char*>(&buffer.at(0)), buffer.size()); isImage = is_file_image(buffer.data()); + file.clear(); + file.seekg(0); } } @@ -252,10 +267,10 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c debug("{} distro_path = {}", __FUNCTION__, distro_path); // this is just for parse() to auto add the distro colors - std::ifstream distro_file(distro_path); - std::string line, _; + std::ifstream distro_file(distro_path); + std::string line, _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ moduleMap, _, layout, tmp_layout, config, false }; while (std::getline(distro_file, line)) { @@ -265,16 +280,15 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c } std::vector<size_t> pureAsciiArtLens; - int maxLineLength = -1; + size_t maxLineLength = 0; + + struct winsize win; + ioctl(STDOUT_FILENO, TIOCGWINSZ, &win); -#if !ANDROID_APP if (isImage) { // clear screen - write(1, "\33[H\33[2J", 7); - - struct winsize win; - ioctl(STDOUT_FILENO, TIOCGWINSZ, &win); + write(STDOUT_FILENO, "\33[H\33[2J", 7); const std::uint16_t font_width = win.ws_xpixel / win.ws_col; const std::uint16_t font_height = win.ws_ypixel / win.ws_row; @@ -284,14 +298,14 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c get_pos(y, x); fmt::print("\033[{};{}H", y, x); - return render_with_image(systemInfo, layout, config, colors, path, font_width, font_height); + return render_with_image(moduleMap, layout, config, path, font_width, font_height); } -#else - if (isImage) + + if (Display::ascii_logo_fd != -1) { - die(_("images are NOT allowed in the android widget at the moment")); + remove(path.c_str()); + close(Display::ascii_logo_fd); } -#endif for (uint i = 0; i < config.logo_padding_top; i++) { @@ -307,59 +321,56 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c std::string line; while (std::getline(file, line)) { - std::string pureOutput; + std::string pureOutput; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, pureOutput, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ moduleMap, pureOutput, layout, tmp_layout, config, false }; std::string asciiArt_s = parse(line, parse_args); parse_args.no_more_reset = false; - if (!config.m_disable_colors) - asciiArt_s += config.gui ? "" : NOCOLOR; - - if (config.gui) - { - // check parse.cpp - const size_t pos = asciiArt_s.rfind("$ </"); - if (pos != std::string::npos) - asciiArt_s.replace(pos, 2, "$"); - } +#if !GUI_APP + if (!config.args_disable_colors) + asciiArt_s += NOCOLOR; +#else + // check parse.cpp + const size_t pos = asciiArt_s.rfind("$ </"); + if (pos != std::string::npos) + asciiArt_s.replace(pos, 2, "$"); +#endif asciiArt.push_back(asciiArt_s); const size_t pureOutputLen = utf8::distance(pureOutput.begin(), pureOutput.end()); - if (static_cast<int>(pureOutputLen) > maxLineLength) - maxLineLength = static_cast<int>(pureOutputLen); + if (pureOutputLen > maxLineLength) + maxLineLength = pureOutputLen; pureAsciiArtLens.push_back(pureOutputLen); debug("asciiArt_s = {}", asciiArt_s); } - if (config.m_print_logo_only) + if (config.args_print_logo_only) return asciiArt; - std::string _; + std::string _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, true }; + parse_args_t parse_args{ moduleMap, _, layout, tmp_layout, config, true }; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; - if (!config.gui && !config.m_disable_colors) - { +#if !GUI_APP + if (!config.args_disable_colors) layout[i].insert(0, NOCOLOR); - } +#endif if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); - i += tmp_layout.size()-1; + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } - auto_colors.clear(); - // erase each element for each instance of MAGIC_LINE layout.erase(std::remove_if(layout.begin(), layout.end(), [](const std::string_view str) { return str.find(MAGIC_LINE) != std::string::npos; }), @@ -368,19 +379,24 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c if (config.logo_position == "top" || config.logo_position == "bottom") { if (!asciiArt.empty()) - layout.insert(config.logo_position == "top" ? layout.begin() : layout.end(), - asciiArt.begin(), asciiArt.end()); + layout.insert(config.logo_position == "top" ? layout.begin() : layout.end(), asciiArt.begin(), + asciiArt.end()); return layout; } + const unsigned int offset = + (config.offset.back() == '%') + ? calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), win.ws_col, maxLineLength) + : std::stoi(config.offset); + size_t i; for (i = 0; i < layout.size(); i++) { size_t origin = config.logo_padding_left; // The user-specified offset to be put before the logo - for (size_t j = 0; j < config.logo_padding_left; j++) - layout.at(i).insert(0, space); + for (size_t j = 0; j < config.logo_padding_left; ++j) + layout.at(i).insert(0, " "); if (i < asciiArt.size()) { @@ -388,26 +404,23 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c origin += asciiArt.at(i).length(); } - const size_t spaces = (maxLineLength + (config.m_disable_source ? 1 : config.offset)) - + const size_t spaces = (maxLineLength + (config.args_disable_source ? 1 : offset)) - (i < asciiArt.size() ? pureAsciiArtLens.at(i) : 0); debug("spaces: {}", spaces); for (size_t j = 0; j < spaces; j++) - layout.at(i).insert(origin, space); + layout.at(i).insert(origin, " "); - if (!config.m_disable_colors) - layout.at(i) += config.gui ? "" : NOCOLOR; +#if !GUI_APP + if (!config.args_disable_colors) + layout.at(i) += NOCOLOR; +#endif } - for (; i < asciiArt.size(); i++) + for (; i < asciiArt.size(); ++i) { - std::string line; - line.reserve(config.logo_padding_left + asciiArt.at(i).length()); - - for (size_t j = 0; j < config.logo_padding_left; j++) - line += space; - + std::string line(config.logo_padding_left, ' '); line += asciiArt.at(i); layout.push_back(line); @@ -419,5 +432,8 @@ std::vector<std::string> Display::render(const Config& config, const colors_t& c void Display::display(const std::vector<std::string>& renderResult) { for (const std::string& str : renderResult) - fmt::print("{}\n", str); + { + fmt::print("{}", str); + fmt::print("\n"); + } } diff --git a/src/fmt/Makefile b/src/fmt/Makefile deleted file mode 100644 index 1bbbbcbd..00000000 --- a/src/fmt/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# Variables taken from {fmt} cmake build files -# I don't wanna use cmake -# cmd: cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF - -CXX ?= g++ -# CXX_DEFINES = -DFMT_LIB_EXPORT -Dfmt_EXPORTS -CXX_INCLUDES = -I../../include -CXX_FLAGS = -O3 -DNDEBUG -fvisibility=hidden -fvisibility-inlines-hidden - -all: fmt - -fmt: format.cc os.cc - $(CXX) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -c format.cc -o format.cc.o - $(CXX) $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -c os.cc -o os.cc.o - ar qc libfmt.a os.cc.o format.cc.o - ranlib libfmt.a - mv -f libfmt.a ../../$(BUILDDIR)/libfmt.a - # $(CXX) -fPIC -O3 -DNDEBUG -shared -Wl,-soname,libfmt.so.10 -o libfmt.so format.cc.o os.cc.o - -clean: - rm -f *.o *.so *.a ../../$(BUILDDIR)/fmt/.*a - -.PHONY: clean all fmt diff --git a/src/gui.cpp b/src/gui.cpp index 1253ee87..7a8e3ce5 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -1,49 +1,55 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#if GUI_APP && !ANDROID_APP +#if GUI_APP #define STB_IMAGE_IMPLEMENTATION #include "gui.hpp" +#include <unistd.h> + #include <array> #include <filesystem> #include <fstream> -#include "config.hpp" #include "display.hpp" #include "fmt/ranges.h" #include "gdkmm/pixbufanimation.h" #include "glibmm/main.h" +#include "glibmm/refptr.h" +#include "gtkmm/cssprovider.h" #include "gtkmm/enums.h" -#include "pangomm/fontdescription.h" -#include "parse.hpp" -#include "query.hpp" #include "stb_image.h" #include "util.hpp" +#include "gdkmm/pixbufanimation.h" +#include "glibmm/refptr.h" +#include "gtkmm/cssprovider.h" +#include "glibmm/main.h" +#include "gtkmm/enums.h" + using namespace GUI; // https://www.codespeedy.com/convert-rgb-to-hex-color-code-in-cpp/ @@ -58,11 +64,11 @@ using namespace GUI; }*/ // Display::render but only for images on GUI -static std::vector<std::string> render_with_image(const Config& config, const colors_t& colors) +static std::vector<std::string> render_with_image(const Config& config) { std::string path{ Display::detect_distro(config) }; - systemInfo_t systemInfo{}; - std::vector<std::string> layout{ config.m_args_layout.empty() ? config.layout : config.m_args_layout }; + moduleMap_t modulesInfo{}; + std::vector<std::string> layout{ config.args_layout.empty() ? config.layout : config.args_layout }; int image_width, image_height, channels; @@ -85,10 +91,10 @@ static std::vector<std::string> render_with_image(const Config& config, const co } // this is just for parse() to auto add the distro colors - std::ifstream file(path, std::ios::binary); - std::string line, _; + std::ifstream file(path, std::ios::binary); + std::string line, _; std::vector<std::string> tmp_layout; - parse_args_t parse_args{ systemInfo, _, layout, tmp_layout, config, colors, false }; + parse_args_t parse_args{ modulesInfo, _, layout, tmp_layout, config, false }; while (std::getline(file, line)) { parse(line, parse_args); @@ -98,13 +104,14 @@ static std::vector<std::string> render_with_image(const Config& config, const co parse_args.parsingLayout = true; for (size_t i = 0; i < layout.size(); ++i) { - layout[i] = parse(layout[i], parse_args); + layout[i] = parse(layout[i], parse_args); parse_args.no_more_reset = false; if (!tmp_layout.empty()) { - layout.erase(layout.begin()+i); - layout.insert(layout.begin()+i, tmp_layout.begin(), tmp_layout.end()); + layout.erase(layout.begin() + i); + layout.insert(layout.begin() + i, tmp_layout.begin(), tmp_layout.end()); + i += tmp_layout.size() - 1; tmp_layout.clear(); } } @@ -114,8 +121,13 @@ static std::vector<std::string> render_with_image(const Config& config, const co [](const std::string_view str) { return str.find(MAGIC_LINE) != std::string::npos; }), layout.end()); + const unsigned int offset = + (config.offset.back() == '%') + ? Display::calc_perc(std::stof(config.offset.substr(0, config.offset.size() - 1)), image_width, 0) + : std::stoi(config.offset); + for (size_t i = 0; i < layout.size(); i++) - for (size_t _ = 0; _ < config.offset; _++) // I use _ because we don't need it + for (size_t _ = 0; _ < offset; _++) // I use _ because we don't need it layout.at(i).insert(0, " "); return layout; @@ -123,39 +135,37 @@ static std::vector<std::string> render_with_image(const Config& config, const co bool Window::set_layout_markup() { - if (isImage) + if (m_isImage) { - if (!m_config.m_print_logo_only) - m_label.set_markup(fmt::format("{}", fmt::join(render_with_image(m_config, m_colors), "\n"))); + if (!m_config.args_print_logo_only) + m_label.set_markup(fmt::format("{}", fmt::join(render_with_image(m_config), "\n"))); } else { - m_label.set_markup(fmt::format("{}", fmt::join(Display::render(m_config, m_colors, true, m_path), "\n"))); + m_label.set_markup(fmt::format("{}", fmt::join(Display::render(m_config, true, m_path, m_moduleMap), "\n"))); } return true; } -Window::Window(const Config& config, const colors_t& colors, const std::string_view path) : - m_config(config), - m_colors(colors), - m_path(path), - isImage(false) +Window::Window(const Config& config, const std::filesystem::path& path, const moduleMap_t& moduleMap) + : m_config(config), m_path(path), m_moduleMap(moduleMap), m_isImage(false) { set_title("customfetch - Higly customizable and fast neofetch like program"); set_default_size(1000, 600); set_position(Gtk::WIN_POS_CENTER_ALWAYS); + set_icon_from_file(ICONPREFIX "/customfetch/Thumbnail.png"); debug("Window::Window analyzing file"); - std::ifstream f(path.data()); + std::ifstream f(path.c_str()); std::array<unsigned char, 32> buffer; f.read(reinterpret_cast<char*>(&buffer.at(0)), buffer.size()); if (is_file_image(buffer.data())) - isImage = true; + m_isImage = true; // useImage can be either a gif or an image - if (isImage && !config.m_disable_source) + if (m_isImage && !config.args_disable_source) { - const auto& img = Gdk::PixbufAnimation::create_from_file(path.data()); + const auto& img = Gdk::PixbufAnimation::create_from_file(path.c_str()); m_img = Gtk::manage(new Gtk::Image(img)); m_img->set(img); m_img->set_alignment(Gtk::ALIGN_CENTER); @@ -164,24 +174,10 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v m_box.set_orientation(Gtk::ORIENTATION_HORIZONTAL); - // https://stackoverflow.com/a/76372996 - Glib::RefPtr<Pango::Context> context = m_label.get_pango_context(); - Pango::FontDescription font(config.font); - debug("font family = {}", font.get_family().raw()); - debug("font style = {}", fmt::underlying(font.get_style())); - debug("font weight = {}", fmt::underlying(font.get_weight())); - context->set_font_description(font); - - /*Gdk::RGBA fg_color; - style_context->lookup_color("theme_fg_color", fg_color); - std::string fg_color_str = rgba_to_hexstr(fg_color);*/ - - auto_colors.clear(); - set_layout_markup(); + this->set_layout_markup(); if (is_live_mode) Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::set_layout_markup), config.loop_ms); - if (config.gui_bg_image != "disable") { if (!std::filesystem::exists(config.gui_bg_image)) @@ -203,6 +199,31 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v m_overlay.add_overlay(m_bg_image); } + if (config.gui_css_file != "disable") + { + if (!std::filesystem::exists(config.gui_css_file)) + die(_("Path to gtk css file '{}' doesn't exist"), config.gui_css_file); + + Glib::RefPtr<Gtk::CssProvider> css_provider = Gtk::CssProvider::create(); + Glib::RefPtr<Gdk::Screen> screen = Gdk::Screen::get_default(); + try + { + css_provider->load_from_path(config.gui_css_file); + } + catch (const Glib::Error& ex) + { + die(_("Failed to load CSS: {}"), ex.gobj()->message); + } + + m_box.get_style_context()->add_provider_for_screen(screen, css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER); + } + + if (Display::ascii_logo_fd != -1) + { + ::remove(path.c_str()); + ::close(Display::ascii_logo_fd); + } + m_box.pack_start(m_label, Gtk::PACK_SHRINK); m_alignment.set(0.5, 0.5, 0, 0); m_alignment.add(m_box); @@ -216,4 +237,4 @@ Window::Window(const Config& config, const colors_t& colors, const std::string_v Window::~Window() {} -#endif // GUI_APP && !ANDROID_APP +#endif // GUI_APP diff --git a/src/fmt/CMakeLists.txt b/src/libs/fmt/CMakeLists.txt similarity index 100% rename from src/fmt/CMakeLists.txt rename to src/libs/fmt/CMakeLists.txt diff --git a/src/libs/fmt/Makefile b/src/libs/fmt/Makefile new file mode 100644 index 00000000..1d291a58 --- /dev/null +++ b/src/libs/fmt/Makefile @@ -0,0 +1,22 @@ +# Variables taken from {fmt} cmake build files +# I don't wanna use cmake +# cmd: cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF + +CXX ?= g++ +CXXSTD ?= c++20 +# CXX_DEFINES = -DFMT_LIB_EXPORT -Dfmt_EXPORTS +CXXFLAGS = -I../../../include/libs -O3 -std=$(CXXSTD) -DNDEBUG $(LTO_FLAGS) -fvisibility=hidden -fvisibility-inlines-hidden -fPIC + +all: fmt + +fmt: format.cc os.cc + $(CXX) $(CXX_DEFINES) $(CXXFLAGS) -c format.cc -o format.cc.o + $(CXX) $(CXX_DEFINES) $(CXXFLAGS) -c os.cc -o os.cc.o + ar qc libfmt.a os.cc.o format.cc.o + ranlib libfmt.a + mv -f libfmt.a ../../../$(BUILDDIR)/libfmt.a + +clean: + rm -f *.o *.so *.a ../../../$(BUILDDIR)/fmt/.*a + +.PHONY: clean all fmt diff --git a/src/fmt/format.cc b/src/libs/fmt/format.cc similarity index 100% rename from src/fmt/format.cc rename to src/libs/fmt/format.cc diff --git a/src/fmt/os.cc b/src/libs/fmt/os.cc similarity index 100% rename from src/fmt/os.cc rename to src/libs/fmt/os.cc diff --git a/src/libs/getopt_port/Makefile b/src/libs/getopt_port/Makefile new file mode 100644 index 00000000..d369ff97 --- /dev/null +++ b/src/libs/getopt_port/Makefile @@ -0,0 +1,14 @@ +CC ?= cc +SRC = getopt.c +TARGET = getopt.o +CFLAGS = -I../../../include/libs $(LTO_FLAGS) -fvisibility=hidden -fPIC + +all: $(TARGET) + +$(TARGET): + $(CC) $(SRC) $(CFLAGS) -c -o ../../../$(BUILDDIR)/$@ + +clean: + rm -rf ../../../$(BUILDDIR)/getopt_port/$(TARGET) + +.PHONY: $(TARGET) clean all diff --git a/src/libs/getopt_port/getopt.c b/src/libs/getopt_port/getopt.c new file mode 100644 index 00000000..6bac813e --- /dev/null +++ b/src/libs/getopt_port/getopt.c @@ -0,0 +1,239 @@ +/******************************************************************************* + * Copyright (c) 2012-2023, Kim Grasman <kim.grasman@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Kim Grasman nor the + * names of contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL KIM GRASMAN BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + ******************************************************************************/ + +#include "getopt.h" + +#include <stddef.h> +#include <string.h> + +char* optarg; +int optopt; +/* The variable optind [...] shall be initialized to 1 by the system. */ +int optind = 1; +int opterr; + +static char* optcursor = NULL; + +/* Implemented based on [1] and [2] for optional arguments. + optopt is handled FreeBSD-style, per [3]. + Other GNU and FreeBSD extensions are purely accidental. + +[1] http://pubs.opengroup.org/onlinepubs/000095399/functions/getopt.html +[2] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html +[3] http://www.freebsd.org/cgi/man.cgi?query=getopt&sektion=3&manpath=FreeBSD+9.0-RELEASE +*/ +int getopt(int argc, char* const argv[], const char* optstring) { + int optchar = -1; + const char* optdecl = NULL; + + optarg = NULL; + opterr = 0; + optopt = 0; + + /* Unspecified, but we need it to avoid overrunning the argv bounds. */ + if (optind >= argc) + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] is a null pointer, getopt() + shall return -1 without changing optind. */ + if (argv[optind] == NULL) + goto no_more_optchars; + + /* If, when getopt() is called *argv[optind] is not the character '-', + getopt() shall return -1 without changing optind. */ + if (*argv[optind] != '-') + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] points to the string "-", + getopt() shall return -1 without changing optind. */ + if (strcmp(argv[optind], "-") == 0) + goto no_more_optchars; + + /* If, when getopt() is called argv[optind] points to the string "--", + getopt() shall return -1 after incrementing optind. */ + if (strcmp(argv[optind], "--") == 0) { + ++optind; + goto no_more_optchars; + } + + if (optcursor == NULL || *optcursor == '\0') + optcursor = argv[optind] + 1; + + optchar = *optcursor; + + /* FreeBSD: The variable optopt saves the last known option character + returned by getopt(). */ + optopt = optchar; + + /* The getopt() function shall return the next option character (if one is + found) from argv that matches a character in optstring, if there is + one that matches. */ + optdecl = strchr(optstring, optchar); + if (optdecl) { + /* [I]f a character is followed by a colon, the option takes an + argument. */ + if (optdecl[1] == ':') { + optarg = ++optcursor; + if (*optarg == '\0') { + /* GNU extension: Two colons mean an option takes an + optional arg; if there is text in the current argv-element + (i.e., in the same word as the option name itself, for example, + "-oarg"), then it is returned in optarg, otherwise optarg is set + to zero. */ + if (optdecl[2] != ':') { + /* If the option was the last character in the string pointed to by + an element of argv, then optarg shall contain the next element + of argv, and optind shall be incremented by 2. If the resulting + value of optind is greater than argc, this indicates a missing + option-argument, and getopt() shall return an error indication. + + Otherwise, optarg shall point to the string following the + option character in that element of argv, and optind shall be + incremented by 1. + */ + if (++optind < argc) { + optarg = argv[optind]; + } else { + /* If it detects a missing option-argument, it shall return the + colon character ( ':' ) if the first character of optstring + was a colon, or a question-mark character ( '?' ) otherwise. + */ + optarg = NULL; + optchar = (optstring[0] == ':') ? ':' : '?'; + } + } else { + optarg = NULL; + } + } + + optcursor = NULL; + } + } else { + /* If getopt() encounters an option character that is not contained in + optstring, it shall return the question-mark ( '?' ) character. */ + optchar = '?'; + } + + if (optcursor == NULL || *++optcursor == '\0') + ++optind; + + return optchar; + +no_more_optchars: + optcursor = NULL; + return -1; +} + +/* Implementation based on [1]. + +[1] http://www.kernel.org/doc/man-pages/online/pages/man3/getopt.3.html +*/ +int getopt_long(int argc, char* const argv[], const char* optstring, + const struct option* longopts, int* longindex) { + const struct option* o = longopts; + const struct option* match = NULL; + int num_matches = 0; + size_t argument_name_length = 0; + size_t option_length = 0; + const char* current_argument = NULL; + int retval = -1; + + optarg = NULL; + optopt = 0; + + if (optind >= argc) + return -1; + + if (strlen(argv[optind]) < 3 || strncmp(argv[optind], "--", 2) != 0) + return getopt(argc, argv, optstring); + + /* It's an option; starts with -- and is longer than two chars. */ + current_argument = argv[optind] + 2; + argument_name_length = strcspn(current_argument, "="); + for (; o->name; ++o) { + /* Check for exact match first. */ + option_length = strlen(o->name); + if (option_length == argument_name_length && + strncmp(o->name, current_argument, option_length) == 0) { + match = o; + num_matches = 1; + break; + } + + /* If not exact, count the number of abbreviated matches. */ + if (strncmp(o->name, current_argument, argument_name_length) == 0) { + match = o; + ++num_matches; + } + } + + if (num_matches == 1) { + /* If longindex is not NULL, it points to a variable which is set to the + index of the long option relative to longopts. */ + if (longindex) + *longindex = (int)(match - longopts); + + /* If flag is NULL, then getopt_long() shall return val. + Otherwise, getopt_long() returns 0, and flag shall point to a variable + which shall be set to val if the option is found, but left unchanged if + the option is not found. */ + if (match->flag) + *(match->flag) = match->val; + + retval = match->flag ? 0 : match->val; + + if (match->has_arg != no_argument) { + optarg = strchr(argv[optind], '='); + if (optarg != NULL) + ++optarg; + + if (match->has_arg == required_argument) { + /* Only scan the next argv for required arguments. Behavior is not + specified, but has been observed with Ubuntu and Mac OSX. */ + if (optarg == NULL && ++optind < argc) { + optarg = argv[optind]; + } + + if (optarg == NULL) + retval = ':'; + } + } else if (strchr(argv[optind], '=')) { + /* An argument was provided to a non-argument option. + I haven't seen this specified explicitly, but both GNU and BSD-based + implementations show this behavior. + */ + retval = '?'; + } + } else { + /* Unknown option or ambiguous match. */ + retval = '?'; + } + + ++optind; + return retval; +} diff --git a/src/libs/json/Makefile b/src/libs/json/Makefile new file mode 100644 index 00000000..4adb0147 --- /dev/null +++ b/src/libs/json/Makefile @@ -0,0 +1,15 @@ +CXX ?= g++ +SRC = json.cpp +TARGET = json.o +CXXSTD ?= c++20 +CXXFLAGS = -I../../../include/libs $(LTO_FLAGS) -std=$(CXXSTD) -fPIC + +all: $(TARGET) + +$(TARGET): + $(CXX) $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ + +clean: + rm -rf ../../$(BUILDDIR)/json/$(TARGET) + +.PHONY: $(TARGET) clean all diff --git a/src/json.cpp b/src/libs/json/json.cpp similarity index 100% rename from src/json.cpp rename to src/libs/json/json.cpp diff --git a/src/libs/tiny-process-library/LICENSE b/src/libs/tiny-process-library/LICENSE new file mode 100644 index 00000000..955aa68b --- /dev/null +++ b/src/libs/tiny-process-library/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015-2020 Ole Christian Eidheim + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/src/libs/tiny-process-library/Makefile b/src/libs/tiny-process-library/Makefile new file mode 100644 index 00000000..688e74fb --- /dev/null +++ b/src/libs/tiny-process-library/Makefile @@ -0,0 +1,36 @@ +CXX ?= g++ +CXXSTD ?= c++20 +CXXFLAGS = -I../../../include/libs/tiny-process-library -std=$(CXXSTD) -O3 -DNDEBUG $(LTO_FLAGS) -fvisibility=default -fvisibility-inlines-hidden -fPIC + +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Windows) + CXXFLAGS += -D_CRT_SECURE_NO_WARNINGS + SRC += process_win.cpp + # Check if we're in MSYS environment + ifneq (,$(findstring MSYS,$(shell uname -o))) + CXXFLAGS += -DMSYS_PROCESS_USE_SH + endif + LIBNAME = tiny-process-library.lib +else + CXXFLAGS += -Wall -Wextra + SRC += process_unix.cpp + LIBNAME = libtiny-process-library.a +endif + +SRC += process.cpp +OBJ = $(SRC:.cpp=.o) + +all: $(LIBNAME) + +$(LIBNAME): $(OBJ) + ar rcs $@ $^ + mv -f $(LIBNAME) ../../../$(BUILDDIR)/$(LIBNAME) + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +clean: + rm -f $(OBJ) $(LIBNAME) + +.PHONY: all clean diff --git a/src/libs/tiny-process-library/process.cpp b/src/libs/tiny-process-library/process.cpp new file mode 100644 index 00000000..a68e5181 --- /dev/null +++ b/src/libs/tiny-process-library/process.cpp @@ -0,0 +1,55 @@ +#include "process.hpp" + +namespace TinyProcessLib { + +Process::Process(const std::vector<string_type> &arguments, const string_type &path, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path); + async_read(); +} + +Process::Process(const std::vector<string_type> &arguments, const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(arguments, path, &environment); + async_read(); +} + +Process::Process(const string_type &command, const string_type &path, + const environment_type &environment, + std::function<void(const char *bytes, size_t n)> read_stdout, + std::function<void(const char *bytes, size_t n)> read_stderr, + bool open_stdin, const Config &config) noexcept + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + open(command, path, &environment); + async_read(); +} + +Process::~Process() noexcept { + close_fds(); +} + +Process::id_type Process::get_id() const noexcept { + return data.id; +} + +bool Process::write(const std::string &str) { + return write(str.c_str(), str.size()); +} + +} // namespace TinyProcessLib diff --git a/src/libs/tiny-process-library/process.hpp b/src/libs/tiny-process-library/process.hpp new file mode 120000 index 00000000..f3906d31 --- /dev/null +++ b/src/libs/tiny-process-library/process.hpp @@ -0,0 +1 @@ +../../../include/libs/tiny-process-library/process.hpp \ No newline at end of file diff --git a/src/libs/tiny-process-library/process_unix.cpp b/src/libs/tiny-process-library/process_unix.cpp new file mode 100644 index 00000000..da4242ce --- /dev/null +++ b/src/libs/tiny-process-library/process_unix.cpp @@ -0,0 +1,535 @@ +#include "process.hpp" +#include <algorithm> +#include <bitset> +#include <cstdlib> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <poll.h> +#include <set> +#include <signal.h> +#include <stdexcept> +#include <string.h> +#include <unistd.h> + +namespace TinyProcessLib { + +static int portable_execvpe(const char *file, char *const argv[], char *const envp[]) { +#ifdef __GLIBC__ + // Prefer native implementation. + return execvpe(file, argv, envp); +#else + if(!file || !*file) { + errno = ENOENT; + return -1; + } + + if(strchr(file, '/') != nullptr) { + // If file contains a slash, no search is needed. + return execve(file, argv, envp); + } + + const char *path = getenv("PATH"); + char cspath[PATH_MAX + 1] = {}; + if(!path) { +// small patch for android +#ifndef __ANDROID__ + // If env variable is not set, use static path string. + confstr(_CS_PATH, cspath, sizeof(cspath)); + path = cspath; +#else + // we are certainly on termux, thus android, + // and it doesn't have _CS_PATH, so here's a fix/workaround + char *prefix = getenv("PREFIX"); + if (prefix) { + strncat(prefix, "/bin", 6); + path = prefix; + } + else { + path = "/data/data/com.termux/files/usr/bin"; + } +#endif + } + + const size_t path_len = strlen(path); + const size_t file_len = strlen(file); + + if(file_len > NAME_MAX) { + errno = ENAMETOOLONG; + return -1; + } + + // Indicates whether we encountered EACCESS at least once. + bool eacces = false; + + const char *curr = nullptr; + const char *next = nullptr; + + for(curr = path; *curr; curr = *next ? next + 1 : next) { + next = strchr(curr, ':'); + if(!next) { + next = path + path_len; + } + + const size_t sz = (next - curr); + if(sz > PATH_MAX) { + // Path is too long. Proceed to next path in list. + continue; + } + + char exe_path[PATH_MAX + 1 + NAME_MAX + 1]; // 1 byte for slash + 1 byte for \0 + memcpy(exe_path, curr, sz); + exe_path[sz] = '/'; + memcpy(exe_path + sz + 1, file, file_len); + exe_path[sz + 1 + file_len] = '\0'; + + execve(exe_path, argv, envp); + + switch(errno) { + case EACCES: + eacces = true; + case ENOENT: + case ESTALE: + case ENOTDIR: + case ENODEV: + case ETIMEDOUT: + // The errors above indicate that the executable was not found. + // The list of errors replicates one from glibc. + // In this case we proceed to next path in list. + break; + + default: + // Other errors indicate that executable was found but failed + // to execute. In this case we return error. + return -1; + } + } + + if(eacces) { + // If search failed, and at least one iteration reported EACCESS, it means + // that the needed executable exists but does not have suitable permissions. + // In this case we report EACEESS for user. + errno = EACCES; + } + // Otherwise we just keep last encountered errno. + return -1; +#endif +} + +Process::Data::Data() noexcept : id(-1) { +} + +Process::Process(const std::function<void()> &function, + std::function<void(const char *, size_t)> read_stdout, + std::function<void(const char *, size_t)> read_stderr, + bool open_stdin, const Config &config) + : closed(true), read_stdout(std::move(read_stdout)), read_stderr(std::move(read_stderr)), open_stdin(open_stdin), config(config) { + if(config.flatpak_spawn_host) + throw std::invalid_argument("Cannot break out of a flatpak sandbox with this overload."); + open(function); + async_read(); +} + +Process::id_type Process::open(const std::function<void()> &function) noexcept { + if(open_stdin) + stdin_fd = std::unique_ptr<fd_type>(new fd_type); + if(read_stdout) + stdout_fd = std::unique_ptr<fd_type>(new fd_type); + if(read_stderr) + stderr_fd = std::unique_ptr<fd_type>(new fd_type); + + int stdin_p[2], stdout_p[2], stderr_p[2]; + + if(stdin_fd && pipe(stdin_p) != 0) + return -1; + if(stdout_fd && pipe(stdout_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + return -1; + } + if(stderr_fd && pipe(stderr_p) != 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + return -1; + } + + id_type pid = fork(); + + if(pid < 0) { + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + return pid; + } + else if(pid == 0) { + if(stdin_fd) + dup2(stdin_p[0], 0); + if(stdout_fd) + dup2(stdout_p[1], 1); + if(stderr_fd) + dup2(stderr_p[1], 2); + if(stdin_fd) { + close(stdin_p[0]); + close(stdin_p[1]); + } + if(stdout_fd) { + close(stdout_p[0]); + close(stdout_p[1]); + } + if(stderr_fd) { + close(stderr_p[0]); + close(stderr_p[1]); + } + + if(!config.inherit_file_descriptors) { + // Optimization on some systems: using 8 * 1024 (Debian's default _SC_OPEN_MAX) as fd_max limit + int fd_max = std::min(8192, static_cast<int>(sysconf(_SC_OPEN_MAX))); // Truncation is safe + if(fd_max < 0) + fd_max = 8192; + for(int fd = 3; fd < fd_max; fd++) + close(fd); + } + + setpgid(0, 0); + // TODO: See here on how to emulate tty for colors: http://stackoverflow.com/questions/1401002/trick-an-application-into-thinking-its-stdin-is-interactive-not-a-pipe + // TODO: One solution is: echo "command;exit"|script -q /dev/null + + if(function) + function(); + + _exit(EXIT_FAILURE); + } + + if(stdin_fd) + close(stdin_p[0]); + if(stdout_fd) + close(stdout_p[1]); + if(stderr_fd) + close(stderr_p[1]); + + if(stdin_fd) + *stdin_fd = stdin_p[1]; + if(stdout_fd) + *stdout_fd = stdout_p[0]; + if(stderr_fd) + *stderr_fd = stderr_p[0]; + + closed = false; + data.id = pid; + return pid; +} + +Process::id_type Process::open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept { + return open([this, &arguments, &path, &environment] { + if(arguments.empty()) + exit(127); + + std::vector<const char *> argv_ptrs; + + if(config.flatpak_spawn_host) { + // break out of sandbox, execute on host + argv_ptrs.reserve(arguments.size() + 3); + argv_ptrs.emplace_back("/usr/bin/flatpak-spawn"); + argv_ptrs.emplace_back("--host"); + } + else + argv_ptrs.reserve(arguments.size() + 1); + + for(auto &argument : arguments) + argv_ptrs.emplace_back(argument.c_str()); + argv_ptrs.emplace_back(nullptr); + + if(!path.empty()) { + if(chdir(path.c_str()) != 0) + exit(1); + } + + if(!environment) + execvp(argv_ptrs[0], const_cast<char *const *>(argv_ptrs.data())); + else { + std::vector<std::string> env_strs; + std::vector<const char *> env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + + portable_execvpe(argv_ptrs[0], const_cast<char *const *>(argv_ptrs.data()), const_cast<char *const *>(env_ptrs.data())); + } + }); +} + +Process::id_type Process::open(const std::string &command, const std::string &path, const environment_type *environment) noexcept { + return open([this, &command, &path, &environment] { + auto command_c_str = command.c_str(); + std::string cd_path_and_command; + if(!path.empty()) { + auto path_escaped = path; + size_t pos = 0; + // Based on https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxsxyb7 + while((pos = path_escaped.find('\'', pos)) != std::string::npos) { + path_escaped.replace(pos, 1, "'\\''"); + pos += 4; + } + cd_path_and_command = "cd '" + path_escaped + "' && " + command; // To avoid resolving symbolic links + command_c_str = cd_path_and_command.c_str(); + } + + if(!environment) { + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execl("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr); + else + execl("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr); + } + else { + std::vector<std::string> env_strs; + std::vector<const char *> env_ptrs; + env_strs.reserve(environment->size()); + env_ptrs.reserve(environment->size() + 1); + for(const auto &e : *environment) { + env_strs.emplace_back(e.first + '=' + e.second); + env_ptrs.emplace_back(env_strs.back().c_str()); + } + env_ptrs.emplace_back(nullptr); + if(config.flatpak_spawn_host) + // break out of sandbox, execute on host + execle("/usr/bin/flatpak-spawn", "/usr/bin/flatpak-spawn", "--host", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + else + execle("/bin/sh", "/bin/sh", "-c", command_c_str, nullptr, env_ptrs.data()); + } + }); +} + +void Process::async_read() noexcept { + if(data.id <= 0 || (!stdout_fd && !stderr_fd)) + return; + + stdout_stderr_thread = std::thread([this] { + std::vector<pollfd> pollfds; + std::bitset<2> fd_is_stdout; + if(stdout_fd) { + fd_is_stdout.set(pollfds.size()); + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stdout_fd, F_SETFL, fcntl(*stdout_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stdout_fd : -1; + pollfds.back().events = POLLIN; + } + if(stderr_fd) { + pollfds.emplace_back(); + pollfds.back().fd = fcntl(*stderr_fd, F_SETFL, fcntl(*stderr_fd, F_GETFL) | O_NONBLOCK) == 0 ? *stderr_fd : -1; + pollfds.back().events = POLLIN; + } + auto buffer = std::unique_ptr<char[]>(new char[config.buffer_size]); + bool any_open = !pollfds.empty(); + while(any_open && (poll(pollfds.data(), static_cast<nfds_t>(pollfds.size()), -1) > 0 || errno == EINTR)) { + any_open = false; + for(size_t i = 0; i < pollfds.size(); ++i) { + if(pollfds[i].fd >= 0) { + if(pollfds[i].revents & POLLIN) { + const ssize_t n = read(pollfds[i].fd, buffer.get(), config.buffer_size); + if(n > 0) { + if(fd_is_stdout[i]) + read_stdout(buffer.get(), static_cast<size_t>(n)); + else + read_stderr(buffer.get(), static_cast<size_t>(n)); + } + else if(n == 0 || (n < 0 && errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + } + else if(pollfds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { + if(fd_is_stdout[i]) { + if(config.on_stdout_close) + config.on_stdout_close(); + } + else { + if(config.on_stderr_close) + config.on_stderr_close(); + } + pollfds[i].fd = -1; + continue; + } + any_open = true; + } + } + } + }); +} + +int Process::get_exit_status() noexcept { + if(data.id <= 0) + return -1; + + int exit_status; + id_type pid; + do { + pid = waitpid(data.id, &exit_status, 0); + } while(pid < 0 && errno == EINTR); + + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, return previously sampled exit status (or -1) + return data.exit_status; + } + else { + // Store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard<std::mutex> lock(close_mutex); + closed = true; + } + close_fds(); + + return exit_status; +} + +bool Process::try_get_exit_status(int &exit_status) noexcept { + if(data.id <= 0) { + exit_status = -1; + return true; + } + + const id_type pid = waitpid(data.id, &exit_status, WNOHANG); + if(pid < 0 && errno == ECHILD) { + // PID doesn't exist anymore, set previously sampled exit status (or -1) + exit_status = data.exit_status; + return true; + } + else if(pid <= 0) { + // Process still running (p==0) or error + return false; + } + else { + // store exit status for future calls + if(exit_status >= 256) + exit_status = exit_status >> 8; + data.exit_status = exit_status; + } + + { + std::lock_guard<std::mutex> lock(close_mutex); + closed = true; + } + close_fds(); + + return true; +} + +void Process::close_fds() noexcept { + if(stdout_stderr_thread.joinable()) + stdout_stderr_thread.join(); + + if(stdin_fd) + close_stdin(); + if(stdout_fd) { + if(data.id > 0) + close(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + if(data.id > 0) + close(*stderr_fd); + stderr_fd.reset(); + } +} + +bool Process::write(const char *bytes, size_t n) { + if(!open_stdin) + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + while(n != 0) { + const ssize_t ret = ::write(*stdin_fd, bytes, n); + if(ret < 0) { + if(errno == EINTR) + continue; + else + return false; + } + bytes += static_cast<size_t>(ret); + n -= static_cast<size_t>(ret); + } + return true; + } + return false; +} + +void Process::close_stdin() noexcept { + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + if(data.id > 0) + close(*stdin_fd); + stdin_fd.reset(); + } +} + +void Process::kill(bool force) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + if(force) { + ::kill(-data.id, SIGTERM); + ::kill(data.id, SIGTERM); // Based on comment in https://gitlab.com/eidheim/tiny-process-library/-/merge_requests/29#note_1146144166 + } + else { + ::kill(-data.id, SIGINT); + ::kill(data.id, SIGINT); + } + } +} + +void Process::kill(id_type id, bool force) noexcept { + if(id <= 0) + return; + + if(force) { + ::kill(-id, SIGTERM); + ::kill(id, SIGTERM); + } + else { + ::kill(-id, SIGINT); + ::kill(id, SIGINT); + } +} + +void Process::signal(int signum) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + ::kill(-data.id, signum); + ::kill(data.id, signum); + } +} + +} // namespace TinyProcessLib diff --git a/src/libs/tiny-process-library/process_win.cpp b/src/libs/tiny-process-library/process_win.cpp new file mode 100644 index 00000000..dd1c3b5e --- /dev/null +++ b/src/libs/tiny-process-library/process_win.cpp @@ -0,0 +1,424 @@ +#include "process.hpp" +// clang-format off +#include <windows.h> +// clang-format on +#include <cstring> +#include <stdexcept> +#include <tlhelp32.h> + +namespace TinyProcessLib { + +Process::Data::Data() noexcept : id(0) {} + +// Simple HANDLE wrapper to close it automatically from the destructor. +class Handle { +public: + Handle() noexcept : handle(INVALID_HANDLE_VALUE) {} + ~Handle() noexcept { + close(); + } + void close() noexcept { + if(handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); + } + HANDLE detach() noexcept { + HANDLE old_handle = handle; + handle = INVALID_HANDLE_VALUE; + return old_handle; + } + operator HANDLE() const noexcept { return handle; } + HANDLE *operator&() noexcept { return &handle; } + +private: + HANDLE handle; +}; + +template <class Char> +struct CharSet; + +template <> +struct CharSet<char> { + static constexpr const char *whitespace = " \t\n\v\""; + static constexpr char space = ' '; + static constexpr char doublequote = '"'; + static constexpr char backslash = '\\'; +}; + +template <> +struct CharSet<wchar_t> { + static constexpr const wchar_t *whitespace = L" \t\n\v\""; + static constexpr wchar_t space = L' '; + static constexpr wchar_t doublequote = L'"'; + static constexpr wchar_t backslash = L'\\'; +}; + +// Based on blog post https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way +template <class Char> +static std::basic_string<Char> join_arguments(const std::vector<std::basic_string<Char>> &args) { + using charset = CharSet<Char>; + + std::basic_string<Char> ret; + + for(const auto &arg : args) { + if(!ret.empty()) + ret.push_back(charset::space); + + if(!arg.empty() && arg.find_first_of(charset::whitespace) == arg.npos) { + ret.append(arg); + continue; + } + + ret.push_back(charset::doublequote); + + for(auto it = arg.begin();; ++it) { + size_t n_backslashes = 0; + + while(it != arg.end() && *it == charset::backslash) { + ++it; + ++n_backslashes; + } + + if(it == arg.end()) { + // Escape all backslashes, but let the terminating double quotation mark + // we add below be interpreted as a metacharacter. + ret.append(n_backslashes * 2, charset::backslash); + break; + } + else if(*it == charset::doublequote) { + // Escape all backslashes and the following double quotation mark. + ret.append(n_backslashes * 2 + 1, charset::backslash); + ret.push_back(*it); + } + else { + // Backslashes aren't special here. + ret.append(n_backslashes, charset::backslash); + ret.push_back(*it); + } + } + + ret.push_back(charset::doublequote); + } + + return ret; +} + +// Based on the discussion thread: https://www.reddit.com/r/cpp/comments/3vpjqg/a_new_platform_independent_process_library_for_c11/cxq1wsj +std::mutex create_process_mutex; + +Process::id_type Process::open(const std::vector<string_type> &arguments, const string_type &path, const environment_type *environment) noexcept { + return open(join_arguments(arguments), path, environment); +} + +// Based on the example at https://msdn.microsoft.com/en-us/library/windows/desktop/ms682499(v=vs.85).aspx. +Process::id_type Process::open(const string_type &command, const string_type &path, const environment_type *environment) noexcept { + if(open_stdin) + stdin_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + if(read_stdout) + stdout_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + if(read_stderr) + stderr_fd = std::unique_ptr<fd_type>(new fd_type(nullptr)); + + Handle stdin_rd_p; + Handle stdin_wr_p; + Handle stdout_rd_p; + Handle stdout_wr_p; + Handle stderr_rd_p; + Handle stderr_wr_p; + + SECURITY_ATTRIBUTES security_attributes; + + security_attributes.nLength = sizeof(SECURITY_ATTRIBUTES); + security_attributes.bInheritHandle = TRUE; + security_attributes.lpSecurityDescriptor = nullptr; + + std::lock_guard<std::mutex> lock(create_process_mutex); + if(stdin_fd) { + if(!CreatePipe(&stdin_rd_p, &stdin_wr_p, &security_attributes, 0) || + !SetHandleInformation(stdin_wr_p, HANDLE_FLAG_INHERIT, 0)) + return 0; + } + if(stdout_fd) { + if(!CreatePipe(&stdout_rd_p, &stdout_wr_p, &security_attributes, 0) || + !SetHandleInformation(stdout_rd_p, HANDLE_FLAG_INHERIT, 0)) { + return 0; + } + } + if(stderr_fd) { + if(!CreatePipe(&stderr_rd_p, &stderr_wr_p, &security_attributes, 0) || + !SetHandleInformation(stderr_rd_p, HANDLE_FLAG_INHERIT, 0)) { + return 0; + } + } + + PROCESS_INFORMATION process_info; + STARTUPINFO startup_info; + + ZeroMemory(&process_info, sizeof(PROCESS_INFORMATION)); + + ZeroMemory(&startup_info, sizeof(STARTUPINFO)); + startup_info.cb = sizeof(STARTUPINFO); + startup_info.hStdInput = stdin_rd_p; + startup_info.hStdOutput = stdout_wr_p; + startup_info.hStdError = stderr_wr_p; + if(stdin_fd || stdout_fd || stderr_fd) + startup_info.dwFlags |= STARTF_USESTDHANDLES; + + if(config.show_window != Config::ShowWindow::show_default) { + startup_info.dwFlags |= STARTF_USESHOWWINDOW; + startup_info.wShowWindow = static_cast<WORD>(config.show_window); + } + + auto process_command = command; +#ifdef MSYS_PROCESS_USE_SH + size_t pos = 0; + while((pos = process_command.find('\\', pos)) != string_type::npos) { + process_command.replace(pos, 1, "\\\\\\\\"); + pos += 4; + } + pos = 0; + while((pos = process_command.find('\"', pos)) != string_type::npos) { + process_command.replace(pos, 1, "\\\""); + pos += 2; + } + process_command.insert(0, "sh -c \""); + process_command += "\""; +#endif + + DWORD creation_flags = stdin_fd || stdout_fd || stderr_fd ? CREATE_NO_WINDOW : 0; // CREATE_NO_WINDOW cannot be used when stdout or stderr is redirected to parent process + string_type environment_str; + if(environment) { +#ifdef UNICODE + for(const auto &e : *environment) + environment_str += e.first + L'=' + e.second + L'\0'; + if(!environment_str.empty()) + environment_str += L'\0'; + creation_flags |= CREATE_UNICODE_ENVIRONMENT; +#else + for(const auto &e : *environment) + environment_str += e.first + '=' + e.second + '\0'; + if(!environment_str.empty()) + environment_str += '\0'; +#endif + } + BOOL bSuccess = CreateProcess(nullptr, process_command.empty() ? nullptr : &process_command[0], nullptr, nullptr, + stdin_fd || stdout_fd || stderr_fd || config.inherit_file_descriptors, // Cannot be false when stdout, stderr or stdin is used + creation_flags, + environment_str.empty() ? nullptr : &environment_str[0], + path.empty() ? nullptr : path.c_str(), + &startup_info, &process_info); + + if(!bSuccess) + return 0; + else + CloseHandle(process_info.hThread); + + if(stdin_fd) + *stdin_fd = stdin_wr_p.detach(); + if(stdout_fd) + *stdout_fd = stdout_rd_p.detach(); + if(stderr_fd) + *stderr_fd = stderr_rd_p.detach(); + + closed = false; + data.id = process_info.dwProcessId; + data.handle = process_info.hProcess; + return process_info.dwProcessId; +} + +void Process::async_read() noexcept { + if(data.id == 0) + return; + + if(stdout_fd) { + stdout_thread = std::thread([this]() { + DWORD n; + std::unique_ptr<char[]> buffer(new char[config.buffer_size]); + for(;;) { + BOOL bSuccess = ReadFile(*stdout_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr); + if(!bSuccess || n == 0) { + if(config.on_stdout_close) + config.on_stdout_close(); + break; + } + read_stdout(buffer.get(), static_cast<size_t>(n)); + } + }); + } + if(stderr_fd) { + stderr_thread = std::thread([this]() { + DWORD n; + std::unique_ptr<char[]> buffer(new char[config.buffer_size]); + for(;;) { + BOOL bSuccess = ReadFile(*stderr_fd, static_cast<CHAR *>(buffer.get()), static_cast<DWORD>(config.buffer_size), &n, nullptr); + if(!bSuccess || n == 0) { + if(config.on_stderr_close) + config.on_stderr_close(); + break; + } + read_stderr(buffer.get(), static_cast<size_t>(n)); + } + }); + } +} + +int Process::get_exit_status() noexcept { + if(data.id == 0) + return -1; + + if(!data.handle) + return data.exit_status; + + WaitForSingleObject(data.handle, INFINITE); + + DWORD exit_status; + if(!GetExitCodeProcess(data.handle, &exit_status)) + data.exit_status = -1; // Store exit status for future calls + else + data.exit_status = static_cast<int>(exit_status); // Store exit status for future calls + + { + std::lock_guard<std::mutex> lock(close_mutex); + CloseHandle(data.handle); + data.handle = nullptr; + closed = true; + } + close_fds(); + + return data.exit_status; +} + +bool Process::try_get_exit_status(int &exit_status) noexcept { + if(data.id == 0) { + exit_status = -1; + return true; + } + + if(!data.handle) { + exit_status = data.exit_status; + return true; + } + + DWORD wait_status = WaitForSingleObject(data.handle, 0); + if(wait_status == WAIT_TIMEOUT) + return false; + + DWORD exit_status_tmp; + if(!GetExitCodeProcess(data.handle, &exit_status_tmp)) + exit_status = -1; + else + exit_status = static_cast<int>(exit_status_tmp); + data.exit_status = exit_status; // Store exit status for future calls + + { + std::lock_guard<std::mutex> lock(close_mutex); + CloseHandle(data.handle); + data.handle = nullptr; + closed = true; + } + close_fds(); + + return true; +} + +void Process::close_fds() noexcept { + if(stdout_thread.joinable()) + stdout_thread.join(); + if(stderr_thread.joinable()) + stderr_thread.join(); + + if(stdin_fd) + close_stdin(); + if(stdout_fd) { + if(*stdout_fd != nullptr) + CloseHandle(*stdout_fd); + stdout_fd.reset(); + } + if(stderr_fd) { + if(*stderr_fd != nullptr) + CloseHandle(*stderr_fd); + stderr_fd.reset(); + } +} + +bool Process::write(const char *bytes, size_t n) { + if(!open_stdin) + throw std::invalid_argument("Can't write to an unopened stdin pipe. Please set open_stdin=true when constructing the process."); + + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + DWORD written; + BOOL bSuccess = WriteFile(*stdin_fd, bytes, static_cast<DWORD>(n), &written, nullptr); + if(!bSuccess || written == 0) { + return false; + } + else { + return true; + } + } + return false; +} + +void Process::close_stdin() noexcept { + std::lock_guard<std::mutex> lock(stdin_mutex); + if(stdin_fd) { + if(*stdin_fd != nullptr) + CloseHandle(*stdin_fd); + stdin_fd.reset(); + } +} + +// Based on http://stackoverflow.com/a/1173396 +void Process::kill(bool /*force*/) noexcept { + std::lock_guard<std::mutex> lock(close_mutex); + if(data.id > 0 && !closed) { + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID == data.id) { + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); + if(process_handle) { + TerminateProcess(process_handle, 2); + CloseHandle(process_handle); + } + } + } while(Process32Next(snapshot, &process)); + } + CloseHandle(snapshot); + } + TerminateProcess(data.handle, 2); + } +} + +// Based on http://stackoverflow.com/a/1173396 +void Process::kill(id_type id, bool /*force*/) noexcept { + if(id == 0) + return; + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if(snapshot) { + PROCESSENTRY32 process; + ZeroMemory(&process, sizeof(process)); + process.dwSize = sizeof(process); + if(Process32First(snapshot, &process)) { + do { + if(process.th32ParentProcessID == id) { + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, process.th32ProcessID); + if(process_handle) { + TerminateProcess(process_handle, 2); + CloseHandle(process_handle); + } + } + } while(Process32Next(snapshot, &process)); + } + CloseHandle(snapshot); + } + HANDLE process_handle = OpenProcess(PROCESS_TERMINATE, FALSE, id); + if(process_handle) + TerminateProcess(process_handle, 2); +} + +} // namespace TinyProcessLib diff --git a/src/libs/toml++/Makefile b/src/libs/toml++/Makefile new file mode 100644 index 00000000..811bdc39 --- /dev/null +++ b/src/libs/toml++/Makefile @@ -0,0 +1,15 @@ +CXX ?= g++ +CXXSTD ?= c++20 +SRC = toml.cpp +TARGET = toml.o +CXXFLAGS = -I../../../include/libs $(LTO_FLAGS) -fvisibility-inlines-hidden -fvisibility=default -fPIC -std=$(CXXSTD) + +all: $(TARGET) + +$(TARGET): + $(CXX) $(SRC) $(CXXFLAGS) -c -o ../../../$(BUILDDIR)/$@ + +clean: + rm -rf ../../../$(BUILDDIR)/toml++/$(TARGET) + +.PHONY: $(TARGET) clean all diff --git a/src/toml++/toml.cpp b/src/libs/toml++/toml.cpp similarity index 100% rename from src/toml++/toml.cpp rename to src/libs/toml++/toml.cpp diff --git a/src/main.cpp b/src/main.cpp index ee6ef3bd..d7b703de 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,45 +1,62 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ -#include <getopt.h> +#include <dlfcn.h> +#include <stdlib.h> +#include <termios.h> #include <unistd.h> +#include <termios.h> +#include <stdlib.h> +#include <algorithm> +#include <cerrno> #include <cstdlib> +#include <cstring> #include <filesystem> -#include <fstream> #include <string> #include <thread> +#include <vector> -#include "config.hpp" +#include "libcufetch/fmt/compile.h" +#include "texts.hpp" +#include "getopt_port/getopt.h" +#include "core-modules.hh" #include "display.hpp" +#include "fmt/base.h" #include "fmt/ranges.h" #include "gui.hpp" -#include "query.hpp" +#include "platform.hpp" #include "switch_fnv1a.hpp" #include "util.hpp" +#if (!__has_include("version.h")) +#error "version.h not found, please generate it with ./scripts/generateVersion.sh" +#else +#include "version.h" +#endif + // clang-format off // https://cfengine.com/blog/2021/optional-arguments-with-getopt-long/ // because "--opt-arg arg" won't work @@ -52,430 +69,87 @@ using namespace std::string_view_literals; -#if ANDROID_APP -#define STRING_IF_ANDROID_APP_ELSE(x) std::string -#define RETURN_OR_PRINT(x) return x -#define RETURN_IF_ANDROID_APP return -#define _true "true" -#define _false "failed" -#else -#define STRING_IF_ANDROID_APP_ELSE(x) x -#define RETURN_OR_PRINT(x) fmt::print("{}\n", x) -#define RETURN_IF_ANDROID_APP -#define _true true -#define _false false -#endif +bool display_modules = false; +bool display_list_logos = false; + +struct termios orig_termios; + +static void disable_raw_mode() +{ + tcsetattr(STDIN_FILENO, TCSAFLUSH, &orig_termios); +} + +static void enable_raw_mode() +{ + tcgetattr(STDIN_FILENO, &orig_termios); + atexit(disable_raw_mode); -static STRING_IF_ANDROID_APP_ELSE(void) version() + struct termios raw = orig_termios; + raw.c_lflag &= ~(ECHO | ICANON); // Disable echo and canonical mode + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +static int kbhit() +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + return select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv) > 0; +} + +// Print the version and some other infos, then exit successfully +static void version() { - std::string version{ "customfetch " VERSION " branch " BRANCH "\n" }; + std::string version{ fmt::format( + "customfetch {} built from branch '{}' at {} commit '{}' ({}).\n" + "Date: {}\n" + "Tag: {}", + VERSION, GIT_BRANCH, GIT_DIRTY, GIT_COMMIT_HASH, GIT_COMMIT_MESSAGE, GIT_COMMIT_DATE, GIT_TAG) }; #if !(USE_DCONF) - version += "NO flags were set\n"; + version += "\n\nNO flags were set\n"; #else - version += "set flags:\n"; + version += "\n\nset flags:\n"; #if USE_DCONF version += "USE_DCONF\n"; #endif #endif - RETURN_OR_PRINT(version); + fmt::print("{}", version); + fmt::print("\n"); // if only everyone would not return error when querying the program version :( std::exit(EXIT_SUCCESS); } -static STRING_IF_ANDROID_APP_ELSE(void) help(bool invalid_opt = false) +// Print the args help menu, then exit with code depending if it's from invalid or -h arg +static void help(bool invalid_opt = false) { - constexpr std::string_view help( -R"(Usage: customfetch [OPTIONS]... -A command-line, GUI app, android widget system information tool (or neofetch like program), which its focus point is customizability and performance. - -NOTE: Arguments that takes [<bool>] values, the values can be either: "true", 1, "enable" or leave it empty. Any other value will be treated as false. - - -n, --no-logo [<bool>] Do not display the logo - -N, --no-color [<bool>] Do not output and parse colors. Useful for stdout or pipe operations - -L, --logo-only [<bool>] Print only the logo - -s, --source-path <path> Path to the ascii art or image file to display - -C, --config <path> Path to the config file to use - -a, --ascii-logo-type [<type>] - The type of ASCII art to apply ("small" or "old"). - Basically will add "_<type>" to the logo filename. - It will return the regular linux ascii art if it doesn't exist. - Leave it empty for regular. - - -D, --data-dir <path> Path to the data dir where we'll taking the distros ascii arts (must contain subdirectory called "ascii") - -d, --distro <name> Print a custom logo from the given `data-dir` (must be the same name, uppercase or lowercase, e.g "windows 11" or "ArCh") - -f, --font <name> The font to be used in the GUI app (syntax must be "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]" without the double quotes and []) - An example: [Liberation Mono] [Normal] [12], which can be "Liberation Mono Normal 12" - - -i, --image-backend <name> (EXPERIMENTAL) Image backend tool for displaying images in terminal. - Right now only 'kitty' and 'viu' are supported - It's recommended to use the GUI app for the moment if something doesn't work - - -m, --layout-line <string> Will replace the config layout, with a layout you specify in the arguments - Example: `customfetch -m "${auto}OS: $<os.name>" -m "${auto}CPU: $<cpu.cpu>"` - Will only print the logo (if not disabled), along side the parsed OS and CPU - - -O, --override <string> Overrides a config value, but NOT arrays. - Syntax must be "name=value" E.g "auto.disk.fmt='Disk(%1): %6'". - For convinience purpose, names that doesn't have a dot for telling the table, will automatically be considered under the [config] table - E.g "sep-reset-after=true" works as "config.sep-reset-after=true" - - -p, --logo-position <value> Position of the logo ("top" or "left" or "bottom") - -o, --offset <num> Offset between the ascii art and the layout - -l, --list-modules Print the list of the info tag modules and its members - --list-logos Print the sorted list of the ascii logos you can you use by the given `data-dir` - -h, --help Print this help menu - -w, --how-it-works Print how customfetch and the layout variable works - -V, --version Print the version along with the git branch it was built - - --loop-ms <num> Execute customfetch in a loop (live mode) every <num> milliseconds. - It won't parse the config every time and will you only notice RAM, uptime etc. changes - Put 0 or a <num> minor than 50 to disable and just print once. - Not availabile in the android widget app. - - --bg-image <path> Path to image to be used in the background in the GUI app (put "disable" for disabling in the config) - --wrap-lines [<bool>] Wrap lines when printing in terminal - --logo-padding-top <num> Padding of the logo from the top - --logo-padding-left <num> Padding of the logo from the left - --layout-padding-top <num> Padding of the layout from the top - --title-sep <string> A character (or string) to use in $<builtin.title_sep> - --sep-reset <string> A character (or string) that when encountered, will automatically reset color - --sep-reset-after [<bool>] Reset color either before or after 'sep-reset' - --gen-config [<path>] Generate default config file to config folder (if path, it will generate to the path) - Will ask for confirmation if file exists already - - --color <string> Replace instances of a color with another value. - Syntax MUST be "name=value" with no space between "=", example: --color "foo=#444333". - Thus replaces any instance of foo with #444333. Can be done with multiple colors separately. - -Read the manual "customfetch.1" or --how-it-works for more infos about customfetch and how it works -)"); - - RETURN_OR_PRINT(help.data()); + fmt::print(FMT_COMPILE("{}"), customfetch_help); + fmt::print("\n"); std::exit(invalid_opt); } -static STRING_IF_ANDROID_APP_ELSE(void) modules_list() +// Print how customfetch works, then exit successfully +static void explain_how_this_works() { - constexpr std::string_view list(R"( ---------------------------------------------------------[ MODULE ONLY ]------------------------------------------------------------------------ -Should be used as like as $<module> -NOTE: module "title_sep" as an extended name version called "title_separator" - -Syntax: -# maybe comments of the module -module: - description [example of what it prints] - -ram: - used and total amount of RAM (auto) with used percentage [2.81 GiB / 15.88 GiB (5.34%)] - -swap: - used and total amount of the swapfile (auto) with used percentage [477.68 MiB / 512.00 MiB (88.45%)] - -# note: the module can have either a device path -# or a filesystem path -# e.g disk(/) or disk(/dev/sda5) -disk(/path/to/fs): - used and total amount of disk space (auto) with type of filesystem and used percentage [379.83 GiB / 438.08 GiB (86.70%) - ext4] - -# usually people have 1 GPU in their PC, -# but if you got more than 1 and want to query it, -# you should call gpu module with a number, e.g gpu1 (default gpu0). -# Infos are gotten from `/sys/class/drm/` and on each cardN directory -gpu: - GPU shorter vendor name and model name [NVIDIA GeForce GTX 1650] - -cpu: - CPU model name with number of virtual processors and max freq [AMD Ryzen 5 5500 (12) @ 4.90 GHz] - -battery: - battery current percentage and status [50.00% [Discharging]] - -title: - user and hostname colored with ${{auto2}} [toni@arch2] - -title_sep: - separator between the title and the system infos (with the title length) [--------] - -colors: - color palette with background spaces - -colors_light: - light color palette with background spaces - -# with `symbol` I mean a symbol to be used for the -# view of the color palette -colors_symbol(symbol): - color palette with specific symbol - -# with `symbol` I mean a symbol to be used for the -# view of the color palette -colors_light_symbol(symbol): - light color palette with specific symbol - ---------------------------------------------------------[ MODULE MEMBERS ]------------------------------------------------------------------------ - -Should be used as like as $<module.member> -NOTE: module members such as "os.pkgs" or "disk.used_perc" have an extended name version - "os.pkgs" == "os.packages" - any module member that has "perc" can be replaced with "percentage" - -Syntax: -# maybe comments of the module -module - member: description [example of what it prints; maybe another] - -os - name: OS name (pretty name) [Ubuntu 22.04.4 LTS; Arch Linux] - name_id: OS name id [ubuntu, arch] - kernel: kernel name and version [Linux 6.9.3-zen1-1-zen] - kernel_name: kernel name [Linux] - kernel_version: kernel version [6.9.3-zen1-1-zen] - version_id: OS version id [22.04.4, 20240101.0.204074] - version_codename: OS version codename [jammy] - pkgs: count of the installed packages by a package manager [1869 (pacman), 4 (flatpak)] - uptime: (auto) uptime of the system [36 mins, 3 hours, 23 days] - uptime_secs: uptime of the system in seconds (should be used along with others uptime_ members) [45] - uptime_mins: uptime of the system in minutes (should be used along with others uptime_ members) [12] - uptime_hours: uptime of the system in hours (should be used along with others uptime_ members) [34] - uptime_days: uptime of the system in days (should be used along with others uptime_ members) [2] - hostname: hostname of the OS [myMainPC] - initsys_name: Init system name [systemd] - initsys_version: Init system version [256.5-1-arch] - -user - name: name you are currently logged in (not real name) [toni69] - shell: login shell name and version [zsh 5.9] - shell_name: login shell [zsh] - shell_path: login shell (with path) [/bin/zsh] - shell_version: login shell version (may be not correct) [5.9] - de_name: Desktop Environment current session name [Plasma] - wm_name: Window Manager current session name [dwm; xfwm4] - wm_version: Window Manager version (may not work correctly) [6.2; 4.18.0] - terminal: terminal name and version [alacritty 0.13.2] - terminal_name: terminal name [alacritty] - terminal_version: terminal version [0.13.2] - -# this module is just for generic theme stuff -# such as indeed cursor -# because it is not GTK-Qt specific -theme - cursor: cursor name with its size (auto add the size if queried) [Bibata-Modern-Ice (16px)] - cursor_name: cursor name [Bibata-Modern-Ice] - cursor_size: cursor size [16] - -# If USE_DCONF flag is set, then we're going to use -# dconf, else backing up to gsettings -theme-gsettings - name: gsettings theme name [Decay-Green] - icons: gsettings icons theme name [Papirus-Dark] - font: gsettings font theme name [Cantarell 10] - cursor: gsettings cursor name with its size (auto add the size if queried) [Bibata-Modern-Ice (16px)] - cursor_name: gsettings cursor name [Bibata-Modern-Ice] - cursor_size: gsettings cursor size [16] - -# the N stands for the gtk version number to query -# so for example if you want to query the gtk3 theme name -# write it like "theme-gtk3.name" -# note: may be slow because of calling "gsettings" if couldn't read from configs. -# Read theme-gsettings module comments -theme-gtkN - name: gtk theme name [Arc-Dark] - icons: gtk icons theme name [Qogir-Dark] - font: gtk font theme name [Noto Sans 10] - -# basically as like as the "theme-gtkN" module above -# but with gtk{2,3,4} and auto format gkt version -# note: may be slow because of calling "gsettings" if couldn't read from configs. -# Read theme-gsettings module comments -theme-gtk-all - name: gtk theme name [Arc-Dark [GTK2/3/4]] - icons: gtk icons theme name [Papirus-Dark [GTK2/3], Qogir [GTK4]] - font: gtk font theme name [Hack Nerd Font 13 [GTK2], Noto Sans 10 [GTK3/4]] - -# note: these members are auto displayed in from B to YB (depending if using SI byte unit or not(IEC)). -# they all (except those that has the same name as the module or that ends with "_perc") -# have variants from -B to -YB and -B to -YiB -# example: if you want to show your 512MiB of used RAM in GiB -# use the `used-GiB` variant (they don't print the unit tho) -ram - used: used amount of RAM (auto) [2.81 GiB] - free: available amount of RAM (auto) [10.46 GiB] - total: total amount of RAM (auto) [15.88 GiB] - used_perc: percentage of used amount of RAM in total [17.69%] - free_perc: percentage of available amount of RAM in total [82.31%] - -# same comments as RAM (above) -swap - used: used amount of the swapfile (auto) [477.68 MiB] - free: available amount of the swapfile (auto) [34.32 MiB] - total: total amount of the swapfile (auto) [512.00 MiB] - used_perc: percentage of used amount of the swapfile in total [93.29%] - free_perc: percentage of available amount of the swapfile in total [6.71%] - -# same comments as RAM (above) -# note: the module can have either a device path -# or a filesystem path -# e.g disk(/) or disk(/dev/sda5) -disk(/path/to/fs) - used: used amount of disk space (auto) [360.02 GiB] - free: available amount of disk space (auto) [438.08 GiB] - total: total amount of disk space (auto) [100.08 GiB] - used_perc: percentage of used amount of the disk in total [82.18%] - free_perc: percentage of available amount of the disk in total [17.82%] - fs: type of filesystem [ext4] - device: path to device [/dev/sda5] - types: an array of type options (pretty format) [Regular, External] - mountdir: path to the device mount point [/] - -# usually people have 1 GPU in their PC, -# but if you got more than 1 and want to query it, -# you should call gpu module with a number, e.g gpu1 (default gpu0). -# Infos are gotten from `/sys/class/drm/` and on each cardN directory -gpu - name: GPU model name [GeForce GTX 1650] - vendor: GPU short vendor name [NVIDIA] - vendor_long: GPU vendor name [NVIDIA Corporation] - -# cpu module has a member called "temp" and it has 3 variant units: -# "temp_C" (Celsius) "temp_F" (Fahrenheit) "temp_K" (Kelvin) -cpu - name: CPU model name [AMD Ryzen 5 5500] - temp: CPU temperature (by the chosen unit) [40.62] - nproc: CPU number of virtual processors [12] - freq_cur: CPU current frequency (in GHz) [3.42] - freq_min: CPU minimum frequency (in GHz) [2.45] - freq_max: CPU maximum frequency (in GHz) [4.90] - freq_bios_limit: CPU frequency limited by bios (in GHz) [4.32] - -# battery module has a member called "temp" and it has 3 variant units: -# "temp_C" (Celsius) "temp_F" (Fahrenheit) "temp_K" (Kelvin) -battery - name: battery model name - temp: battery temperature (by the chosen unit) - perc: battery current percentage - vendor: battery manufacturer name - status: battery current status [Discharging, AC Connected] - technology: battery technology [Li-lion] - capacity_level: battery capacity level [Normal, Critical] - -system - host: Host (aka. Motherboard) model name with vendor and version [Micro-Star International Co., Ltd. PRO B550M-P GEN3 (MS-7D95) 1.0] - host_name: Host (aka. Motherboard) model name [PRO B550M-P GEN3 (MS-7D95)] - host_version: Host (aka. Motherboard) model version [1.0] - host_vendor: Host (aka. Motherboard) model vendor [Micro-Star International Co., Ltd.] - arch: the architecture of the machine [x86_64, aarch64] - -)"); - - RETURN_OR_PRINT(list.data()); + fmt::print(FMT_COMPILE("{}"), explain_customfetch); + fmt::print("\n"); std::exit(EXIT_SUCCESS); } -static STRING_IF_ANDROID_APP_ELSE(void) explain_how_this_works() +// Print a modules list of ascii logos you can use at a "data-dir" +// @param data_dir The data directory +static void list_logos(const std::string& data_dir) { - constexpr std::string_view str( -R"( -customfetch is designed with customizability in mind -here is how it works: -the variable "layout" is used for showing the infos as like as the user want, no limitation. -inside here there are 5 "tags": $<> $() ${} $[] $%% - -The Info tag $<> lets you print the value of a member of a module. -e.g $<user.name> will print the username, $<os.kernel_version> will print the kernel version and so on. - -There are variants who you only need the module name, -such as $<ram> or $<title> -All the modules and their members are listed in the `--list-modules` argument - -The Bash command tag $() will execute bash commands and print the output. -e.g $(echo \"hello world\") will indeed echo out hello world. -you can even use pipes -e.g $(echo \"hello world\" | cut -d' ' -f2) will only print world - -The Conditional tag $[] is used to display different outputs based on the comparison. -syntax MUST be $[something,equalToSomethingElse,iftrue,ifalse] -note: putting spaces between commas, could change the expected result - -Each part can have a tag or anything else. -e.g $[$<user.name>,$(echo $USER),the name is correct,the name is NOT correct] - -This is useful when on some terminal or WM the detection can be different than others -Or maybe even on holidays for printing special texts -e.g $[$(date +%d-%m),25-12,${red}Happy ${white}Christmas!,] - -The Color tag ${} is used for printing the text of a certain color. -e.g "${red}hello world" will indeed print "hello world" in red (or the color you set in the variable) -The colors can be predefined such as: black, red, green, blue, cyan, yellow, magenta, white. -They can be configured in the config file. - -They can have hex codes colors (e.g "#5522dd"). -You can apply special effects to colors by using the following symbols before the '#' in hex codes: - - Terminal and GUI app GUI app only -* b - for background color. * o - for overline -* u - to underline the text * a(value) - for fg alpha (either a plain integer between 1 and 65536 or a percentage value like `50%`) -* ! - for bold text * L(value) - to underline the text with a style (`none`, `single`, `double`, `low`, `error`) -* i - for italic text * U(value) - for choosing the underline color (hexcode color) -* s - for strikethrough text * B(value) - for bg color text (hexcode color) - * S(value) - for strikethrough color (hexcode color) - Terminal Only * O(value) - for overline color (hexcode color) -* l - for blinking text * A(value) - for bg alpha (either a plain integer between 1 and 65536 or a percentage value like `50%`) - * w(value) - for specify font weight (`ultralight`, `light`, `normal`, `bold`, `ultrabold`, `heavy`, or a numeric weight) - -Alternatively, ANSI escape codes can be used, e.g ${\e[1;32m} or ${\e[38;2;34;255;11m}. - -To reset colors, use ${0} for a normal reset or ${1} for a bold reset. - -To use the colors that the ascii art logo uses, use ${auto} for getting the 1st color, ${auto4} for the 4th one and so on. -If you're using the GUI app and wants to display a custom source that's an image, all the auto colors will be the same colors as the distro ones - -The Percentage tag $%% is used for displaying the percentage between 2 numbers.\ -It **Must** contain a comma for separating the 2. They can be either be taken from a tag or it put yourself.\ -For example: $%50,100% -For inverting colors of bad and great (red and green), before the first '%' put '!' -without quotes ofc - -################################################################ -# Little FAQ -# Q: Why when I use & or < in the config or ASCII art, it won't work on the GUI app? -# A: replace "<" with "\\<" in the config, or "\<" in the ascii art. Same goes for & -# It won't affect the printing in terminal -# -# Q: I want to use `cbonsai` as ASCII art, how do I use it? -# A: First off, create a text file and there put only `$(!cbonsai -p)` -# Save the file and use `-s "/path/to/text/file"`. -# Use `--offset` (`-o`) for aligning and put it under the bonsai. -# -# Read the manual customfetch.1 for more infos with $() tag -# -# Q: Can I use recursive tags? -# A: If "$<disk($<disk($[1,1,$(echo -n $<disk(/).mountdir>),23]).mountdir>)>" works, -# Then I guess yeah -################################################################ - -)"); - - RETURN_OR_PRINT(str.data()); - std::exit(EXIT_SUCCESS); -} - -static STRING_IF_ANDROID_APP_ELSE(void) list_logos(const Config& config) -{ - if (access(config.data_dir.c_str(), F_OK) != 0) - { -#if ANDROID_APP - return fmt::format("failed to access data directory {}", config.data_dir); -#else - die("failed to access data directory {}", config.data_dir); -#endif - } + debug("data-dir = {}", data_dir); + if (access(data_dir.c_str(), F_OK) != 0) + die("failed to access data directory '{}'", data_dir); std::vector<std::string> list; - for (const auto& logo : std::filesystem::directory_iterator{config.data_dir+"/ascii"}) + for (const auto& logo : std::filesystem::directory_iterator{ data_dir }) { if (logo.is_regular_file()) list.push_back(logo.path().stem()); @@ -483,19 +157,75 @@ static STRING_IF_ANDROID_APP_ELSE(void) list_logos(const Config& config) std::sort(list.begin(), list.end()); - RETURN_OR_PRINT(fmt::format("{}", fmt::join(list, "\n"))); - std::exit(EXIT_SUCCESS); + fmt::print("{}", fmt::join(list, "\n")); + fmt::print("\n"); } +// Print all info modules you can put in $<>, then exit successfully +static void modules_list() +{ + fmt::print("{}", R"(=============================================================================================== +| Syntax: | +| when "[NO QUERY]" is present, it means you should call only the children modules (if any) | +| | +| module - Root module description [NO QUERY] | +| foo - Submodule with no children description | +| subfoo - Submodule with children description | +| subbar - Submodule children of "subfoo" | +=============================================================================================== +)"); + + for (const module_t& module : cfGetModules()) + { + std::vector<std::string> parts; + + // Split name into parts (e.g., "os.name.pretty" -> ["os", "name", "pretty"]) + size_t start = 0, end = module.name.find('.'); + bool new_module = true; + while (end != std::string::npos) + { + new_module = false; + parts.push_back(module.name.substr(start, end - start)); + start = end + 1; + end = module.name.find('.', start); + } + parts.push_back(module.name.substr(start)); + if (new_module) + fmt::print("\n"); + + // Generate indentation + for (size_t depth = 0; depth < parts.size(); ++depth) + { + if (depth == parts.size() - 1) + { + if (new_module) + fmt::print("{} - {}", parts[depth], module.description); + else + fmt::print("{:<6} \t- {}", parts[depth], module.description); + + if (!module.handler) + fmt::print(" [NO QUERY]"); + } + else + { + fmt::print(" "); + } + } + + fmt::print("\n"); + } +} + +// clang-format off +// Return true if optarg says something true static bool str_to_bool(const std::string_view str) { return (str == "true" || str == "1" || str == "enable"); } -// clang-format off // parseargs() but only for parsing the user config path trough args // and so we can directly construct Config -static std::string parse_config_path(int argc, char* argv[], const std::string& configDir) +static std::filesystem::path parse_config_path(int argc, char* argv[], const std::filesystem::path &configDir) { int opt = 0; int option_index = 0; @@ -515,31 +245,31 @@ static std::string parse_config_path(int argc, char* argv[], const std::string& case '?': break; - case 'C': + case 'C': if (!std::filesystem::exists(optarg)) die(_("config file '{}' doesn't exist"), optarg); return optarg; } } - return configDir + "/config.toml"; + return configDir / "config.toml"; } -static STRING_IF_ANDROID_APP_ELSE(bool) parseargs(int argc, char* argv[], Config& config, const std::string_view configFile) +static bool parseargs(int argc, char* argv[], Config& config, const std::filesystem::path& configFile) { int opt = 0; int option_index = 0; opterr = 1; // re-enable since before we disabled for "invalid option" error - const char *optstring = "-VhwnLlNa::f:o:C:O:i:d:D:p:s:m:"; + const char *optstring = "-VhwnLlNa:f:o:C:O:i:d:D:p:s:m:"; static const struct option opts[] = { {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, 'h'}, {"list-modules", no_argument, 0, 'l'}, {"how-it-works", no_argument, 0, 'w'}, - {"logo-only", optional_argument, 0, 'L'}, - {"no-logo", optional_argument, 0, 'n'}, - {"no-color", optional_argument, 0, 'N'}, - {"ascii-logo-type", optional_argument, 0, 'a'}, + {"logo-only", no_argument, 0, 'L'}, + {"no-logo", no_argument, 0, 'n'}, + {"no-color", no_argument, 0, 'N'}, + {"ascii-logo-type", required_argument, 0, 'a'}, {"offset", required_argument, 0, 'o'}, {"override", required_argument, 0, 'O'}, {"font", required_argument, 0, 'f'}, @@ -551,24 +281,27 @@ static STRING_IF_ANDROID_APP_ELSE(bool) parseargs(int argc, char* argv[], Config {"source-path", required_argument, 0, 's'}, {"image-backend", required_argument, 0, 'i'}, - {"list-logos", no_argument, 0, "list-logos"_fnv1a16}, - {"sep-reset-after", optional_argument, 0, "sep-reset-after"_fnv1a16}, - {"wrap-lines", optional_argument, 0, "wrap-lines"_fnv1a16}, - {"gen-config", optional_argument, 0, "gen-config"_fnv1a16}, - {"sep-reset", required_argument, 0, "sep-reset"_fnv1a16}, - {"title-sep", required_argument, 0, "title-sep"_fnv1a16}, - {"logo-padding-top", required_argument, 0, "logo-padding-top"_fnv1a16}, - {"logo-padding-left", required_argument, 0, "logo-padding-left"_fnv1a16}, - {"layout-padding-top", required_argument, 0, "layout-padding-top"_fnv1a16}, - {"loop-ms", required_argument, 0, "loop-ms"_fnv1a16}, - {"bg-image", required_argument, 0, "bg-image"_fnv1a16}, - {"color", required_argument, 0, "color"_fnv1a16}, + {"list-logos", no_argument, 0, "list-logos"_fnv1a16}, + {"disallow-command-tag", no_argument, 0, "disallow-command-tag"_fnv1a16}, + {"sep-reset-after", optional_argument, 0, "sep-reset-after"_fnv1a16}, + {"debug", optional_argument, 0, "debug"_fnv1a16}, + {"wrap-lines", optional_argument, 0, "wrap-lines"_fnv1a16}, + {"gen-config", optional_argument, 0, "gen-config"_fnv1a16}, + {"sep-reset", required_argument, 0, "sep-reset"_fnv1a16}, + {"title-sep", required_argument, 0, "title-sep"_fnv1a16}, + {"logo-padding-top", required_argument, 0, "logo-padding-top"_fnv1a16}, + {"logo-padding-left", required_argument, 0, "logo-padding-left"_fnv1a16}, + {"layout-padding-top", required_argument, 0, "layout-padding-top"_fnv1a16}, + {"gtk-css", required_argument, 0, "gtk-css"_fnv1a16}, + {"loop-ms", required_argument, 0, "loop-ms"_fnv1a16}, + {"bg-image", required_argument, 0, "bg-image"_fnv1a16}, + {"color", required_argument, 0, "color"_fnv1a16}, {0,0,0,0} }; /* parse operation */ - optind = 0; + optind = 1; while ((opt = getopt_long(argc, argv, optstring, opts, &option_index)) != -1) { switch (opt) @@ -576,87 +309,90 @@ static STRING_IF_ANDROID_APP_ELSE(bool) parseargs(int argc, char* argv[], Config case 0: break; case '?': - RETURN_IF_ANDROID_APP help(EXIT_FAILURE); break; + help(EXIT_FAILURE); break; case 'V': - RETURN_IF_ANDROID_APP version(); break; + version(); break; case 'h': - RETURN_IF_ANDROID_APP help(); break; + help(); break; case 'l': - RETURN_IF_ANDROID_APP modules_list(); break; + display_modules = true; break; case 'w': - RETURN_IF_ANDROID_APP explain_how_this_works(); break; + explain_how_this_works(); break; case "list-logos"_fnv1a16: - RETURN_IF_ANDROID_APP list_logos(config); break; + display_list_logos = true; break; case 'f': - config.overrides["gui.font"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("gui.font", optarg); break; case 'o': - config.overrides["config.offset"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.offset", optarg); break; case 'C': // we have already did it in parse_config_path() break; case 'D': - config.overrides["config.data-dir"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.data-dir", optarg); break; case 'd': - config.m_custom_distro = str_tolower(optarg); break; + config.args_custom_distro = str_tolower(optarg); break; case 'm': - config.m_args_layout.push_back(optarg); break; + config.args_layout.push_back(optarg); break; case 'p': - config.overrides["config.logo-position"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.logo-position", optarg); break; case 's': - config.overrides["config.source-path"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("config.source-path", optarg); break; case 'i': - config.m_image_backend = optarg; break; + config.args_image_backend = optarg; break; case 'O': config.overrideOption(optarg); break; case 'N': - if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.m_disable_colors = str_to_bool(optarg); - else - config.m_disable_colors = true; - break; + config.args_disable_colors = true; break; case 'a': - if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.overrides["config.ascii-logo-type"] = {.value_type = STR, .string_value = optarg}; - else - config.overrides["config.ascii-logo-type"] = {.value_type = STR, .string_value = ""}; - break; + config.overrideOption("config.ascii-logo-type", optarg); break; case 'n': - if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.m_disable_source = str_to_bool(optarg); - else - config.m_disable_source = true; - break; + config.args_disable_source = true; break; case 'L': - if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.m_print_logo_only = str_to_bool(optarg); - else - config.m_print_logo_only = true; - break; + config.args_print_logo_only = true; break; + + case "disallow-command-tag"_fnv1a16: + config.args_disallow_commands = true; break; case "logo-padding-top"_fnv1a16: - config.overrides["config.logo-padding-top"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.logo-padding-top", std::stoi(optarg)); break; case "logo-padding-left"_fnv1a16: - config.overrides["config.logo-padding-left"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.logo-padding-left", std::stoi(optarg)); break; case "layout-padding-top"_fnv1a16: - config.overrides["config.layout-padding-top"] = {.value_type = INT, .int_value = std::stoi(optarg)}; break; + config.overrideOption("config.layout-padding-top", std::stoi(optarg)); break; case "loop-ms"_fnv1a16: config.loop_ms = std::stoul(optarg); break; + case "color"_fnv1a16: + config.addAliasColors(optarg); break; + + case "sep-reset"_fnv1a16: + config.overrideOption("config.sep-reset", optarg); break; + + case "title-sep"_fnv1a16: + config.overrideOption("config.title-sep", optarg); break; + case "bg-image"_fnv1a16: - config.overrides["gui.bg-image"] = {.value_type = STR, .string_value = optarg}; break; + config.overrideOption("gui.bg-image", optarg); break; + + case "gtk-css"_fnv1a16: + config.overrideOption("gui.gtk-css", optarg); break; case "wrap-lines"_fnv1a16: if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.overrides["config.wrap-lines"] = {.value_type = BOOL, .bool_value = str_to_bool(optarg)}; + config.overrideOption("config.wrap-lines", str_to_bool(optarg)); else - config.overrides["config.wrap-lines"] = {.value_type = BOOL, .bool_value = true}; + config.overrideOption("config.wrap-lines", true); break; - case "color"_fnv1a16: - config.addAliasColors(optarg); break; + case "debug"_fnv1a16: + if (OPTIONAL_ARGUMENT_IS_PRESENT) + debug_print = str_to_bool(optarg); + else + debug_print = true; + break; case "gen-config"_fnv1a16: if (OPTIONAL_ARGUMENT_IS_PRESENT) @@ -665,25 +401,26 @@ static STRING_IF_ANDROID_APP_ELSE(bool) parseargs(int argc, char* argv[], Config config.generateConfig(configFile); exit(EXIT_SUCCESS); - case "sep-reset"_fnv1a16: - config.overrides["config.sep-reset"] = {.value_type = STR, .string_value = optarg}; break; - - case "title-sep"_fnv1a16: - config.overrides["config.title-sep"] = {.value_type = STR, .string_value = optarg}; break; - case "sep-reset-after"_fnv1a16: if (OPTIONAL_ARGUMENT_IS_PRESENT) - config.overrides["config.sep-reset-after"] = {.value_type = BOOL, .bool_value = str_to_bool(optarg)}; + config.overrideOption("config.sep-reset-after", str_to_bool(optarg)); else - config.overrides["config.sep-reset-after"] = {.value_type = BOOL, .bool_value = true}; + config.overrideOption("config.sep-reset-after", true); break; default: - return _false; + return false; } } - return _true; + config.overrideOption("intern.args.print-logo-only", config.args_print_logo_only); + config.overrideOption("intern.args.disable-logo", config.args_disable_source); + config.overrideOption("intern.args.disallow-commands", config.args_disallow_commands); + config.overrideOption("intern.args.custom-distro", config.args_custom_distro); + config.overrideOption("intern.args.image-backend", config.args_image_backend); + config.overrideOption("intern.args.disable-colors", config.args_disable_colors); + + return true; } static void enable_cursor() @@ -697,7 +434,7 @@ static void enable_cursor() // taken from pacman static void localize(void) { -#if ENABLE_NLS +#if ENABLE_NLS && !CF_MACOS static bool init = false; if (!init) { @@ -709,69 +446,82 @@ static void localize(void) #endif } -#if ANDROID_APP -std::string mainAndroid_and_render(int argc, char *argv[], JNIEnv *env, jobject obj, bool do_not_load_config) -{ - jni_objs = {env, obj}; - // reset option index - optind = 0; -#else -int main(int argc, char *argv[]) +// clang-format on +int main(int argc, char* argv[]) { -#endif + const std::filesystem::path& configDir = getConfigDir(); + const std::filesystem::path& configFile = parse_config_path(argc, argv, configDir); -#ifdef VENDOR_TEST - // test - fmt::println("=== VENDOR TEST! ==="); + localize(); - fmt::println("Intel: {}", binarySearchPCIArray("8086")); - fmt::println("AMD: {}", binarySearchPCIArray("1002")); - fmt::println("NVIDIA: {}", binarySearchPCIArray("10de")); -#endif + Config config(configFile, configDir); + if (!parseargs(argc, argv, config, configFile)) + return 1; + config.loadConfigFile(configFile); -#ifdef DEVICE_TEST - // test - fmt::println("=== DEVICE TEST! ==="); + std::vector<void*> plugins_handle; + const std::filesystem::path pluginDir = configDir / "plugins"; + std::filesystem::create_directories(pluginDir); + for (const auto& entry : std::filesystem::recursive_directory_iterator{ pluginDir }) + { + if (entry.is_regular_file() && entry.path().has_extension() && entry.path().extension() == LIBRARY_EXTENSION){} + else {continue;} - fmt::println("an Intel iGPU: {}", binarySearchPCIArray("8086", "0f31")); - fmt::println("RX 7700 XT: {}", binarySearchPCIArray("1002", "747e")); - fmt::println("GTX 1650: {}", binarySearchPCIArray("10de", "1f0a")); - fmt::println("?: {}", binarySearchPCIArray("1414", "0006")); -#endif + debug("loading plugin at {}!", entry.path().string()); - // clang-format on - colors_t colors; + void* handle = LOAD_LIBRARY(std::filesystem::absolute(entry.path()).c_str()); + if (!handle) + { + // dlerror() is pretty formatted + warn("Failed to load plugin at '{}': {}", entry.path().string(), dlerror()); + dlerror(); + continue; + } - const std::string& configDir = getConfigDir(); - const std::string& configFile = parse_config_path(argc, argv, configDir); + LOAD_LIB_SYMBOL(handle, void, start, void*, const ConfigBase&); + if (dlerror()) + { + warn("Failed to load plugin at '{}': Missing function 'start'", entry.path().string()); + dlclose(handle); + continue; + } - localize(); + start(handle, config); + plugins_handle.push_back(handle); + } -#if ANDROID_APP - Config config(configFile, configDir); - const std::string& parseargs_ret = parseargs(argc, argv, config, configFile); - if (parseargs_ret != _true) - return parseargs_ret; + // The "conflicting" modules won't be overwritten by the main ones. + // First the external modules, then the core ones. + core_plugins_start(config); - if (!do_not_load_config) - config.loadConfigFile(configFile, colors); + if (display_modules) + { + modules_list(); + return 0; + } + else if (display_list_logos) + { + list_logos(config.data_dir + "/ascii"); + return 0; + } - // since ANDROID_APP means that it will run as an android widget, so in GUI, - // then let's make it always true - config.gui = true; - config.wrap_lines = true; -#else - Config config(configFile, configDir); - if (!parseargs(argc, argv, config, configFile)) - return 1; + const std::vector<module_t>& modules = cfGetModules(); + moduleMap_t moduleMap; + + debug("modules count: {}", modules.size()); + for (const module_t& module : modules) + { + debug("adding module {} (has handler: {})", module.name, module.handler != NULL); + if (!module.handler) + continue; - config.loadConfigFile(configFile, colors); -#endif // ANDROID_APP + moduleMap.emplace(module.name, module); + } - is_live_mode = (config.loop_ms > 50); + is_live_mode = (config.loop_ms >= 200); if (config.source_path.empty() || config.source_path == "off") - config.m_disable_source = true; + config.args_disable_source = true; config.m_display_distro = (config.source_path == "os"); @@ -793,21 +543,24 @@ int main(int argc, char *argv[]) debug("{} path = {}", __PRETTY_FUNCTION__, path); - if (!std::filesystem::exists(path) && !config.m_disable_source) - die(_("Path '{}' doesn't exist. Can't load logo"), path); + if (!std::filesystem::exists(path) && !config.args_disable_source) + { + path = std::filesystem::temp_directory_path() / "customfetch_ascii_logo-XXXXXX"; + Display::ascii_logo_fd = mkstemp(path.data()); + if (Display::ascii_logo_fd < 0) + die("Failed to create temp path at {}: {}", path, strerror(errno)); + write(Display::ascii_logo_fd, ascii_logo.data(), ascii_logo.size()); + } -#if !ANDROID_APP #if GUI_APP - config.gui = true; const auto& app = Gtk::Application::create("org.toni.customfetch"); - GUI::Window window(config, colors, path); + GUI::Window window(config, path, moduleMap); return app->run(window); #endif // GUI_APP if (!config.wrap_lines) { // https://en.cppreference.com/w/c/program/exit - // if something goes wrong like a segfault, then re-enable the cursor again std::atexit(enable_cursor); // hide cursor and disable line wrapping @@ -816,31 +569,54 @@ int main(int argc, char *argv[]) if (is_live_mode) { - const std::chrono::milliseconds sleep_ms {config.loop_ms}; + enable_raw_mode(); + const std::chrono::milliseconds sleep_ms{ config.loop_ms }; while (true) { + if (kbhit()) + { + char c; + read(STDIN_FILENO, &c, 1); + if (c == 'q' || c == 'Q') + { + info("exiting...\n"); + disable_raw_mode(); + break; + } + } + // clear screen and go to position 0, 0 - write(1, "\33[H\33[2J", 7); + write(STDOUT_FILENO, "\33[H\33[2J", 7); fmt::print("\033[0;0H"); - Display::display(Display::render(config, colors, false, path)); + Display::display(Display::render(config, false, path, moduleMap)); std::this_thread::sleep_for(sleep_ms); } } else { - Display::display(Display::render(config, colors, false, path)); + Display::display(Display::render(config, false, path, moduleMap)); } // enable both of them again if (!config.wrap_lines) enable_cursor(); - return 0; -#else + core_plugins_finish(); + for (void* handle : plugins_handle) + { + LOAD_LIB_SYMBOL(handle, void, finish, void*); + if (dlerror()) + { + dlclose(handle); + continue; + } - return fmt::format("{}", fmt::join(Display::render(config, colors, false, path), "<br>")); + finish(handle); + UNLOAD_LIBRARY(handle); + } + plugins_handle.clear(); -#endif // !ANDROID_APP + return 0; } diff --git a/src/parse.cpp b/src/parse.cpp deleted file mode 100644 index a1754038..00000000 --- a/src/parse.cpp +++ /dev/null @@ -1,1939 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "parse.hpp" - -#include <unistd.h> - -#include <algorithm> -#include <array> -#include <chrono> -#include <cstdint> -#include <cstdlib> -#include <ios> -#include <optional> -#include <sstream> -#include <string> -#include <string_view> -#include <vector> - -#include "config.hpp" -#include "fmt/color.h" -#include "fmt/format.h" -#include "fmt/ranges.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -class Parser -{ -public: - Parser(const std::string_view src, std::string& pureOutput) : src{ src }, pureOutput{ pureOutput } {} - - bool try_read(const char c) - { - if (is_eof()) - return false; - - if (src[pos] == c) - { - ++pos; - return true; - } - - return false; - } - - char read_char(const bool add_pureOutput = false) - { - if (is_eof()) - return 0; - - if (add_pureOutput) - pureOutput += src[pos]; - - ++pos; - return src[pos - 1]; - } - - bool is_eof() - { return pos >= src.length(); } - - void rewind(const size_t count = 1) - { pos -= std::min(pos, count); } - - const std::string_view src; - std::string& pureOutput; - size_t dollar_pos = 0; - size_t pos = 0; -}; - -// declarations of static members in query.hpp -Query::System::System_t Query::System::m_system_infos; -Query::Theme::Theme_t Query::Theme::m_theme_infos; -Query::User::User_t Query::User::m_users_infos; -Query::Battery::Battery_t Query::Battery::m_battery_infos; -Query::CPU::CPU_t Query::CPU::m_cpu_infos; -Query::RAM::RAM_t Query::RAM::m_memory_infos; -Query::GPU::GPU_t Query::GPU::m_gpu_infos; -Query::Disk::Disk_t Query::Disk::m_disk_infos; - -struct statvfs Query::Disk::m_statvfs; -struct utsname Query::System::m_uname_infos; -struct sysinfo Query::System::m_sysInfos; -struct passwd* Query::User::m_pPwd; - -bool Query::System::m_bInit = false; -bool Query::RAM::m_bInit = false; -bool Query::CPU::m_bInit = false; -bool Query::User::m_bInit = false; -bool Query::Battery::m_bInit = false; -bool Query::User::m_bDont_query_dewm = false; - -// useless useful tmp string for parse() without using the original -// pureOutput -std::string _; - -static std::array<std::string, 3> get_ansi_color(const std::string_view str, const colors_t& colors) -{ - const size_t first_m = str.rfind('m'); - if (first_m == std::string::npos) - die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '{}'"), str); - - std::string col = str.data(); - col.erase(first_m); // 1;42 - -#if !ANDROID_APP - std::string weight = hasStart(col, "1;") ? "bold" : "normal"; - std::string type = "fgcolor"; // either fgcolor or bgcolor - - if (hasStart(col, "1;") || hasStart(col, "0;")) - col.erase(0, 2); - - debug("col = {}", col); - const int n = std::stoi(col); - - // unfortunatly you can't do bold and light in pango - if ((n >= 100 && n <= 107) || (n >= 90 && n <= 97)) - weight = "light"; - - if ((n >= 100 && n <= 107) || (n >= 40 && n <= 47)) - type = "bgcolor"; -#else - std::string weight = hasStart(col, "1;") ? "<b>" : ""; - std::string type = "color"; // either color or background-color - - if (hasStart(col, "1;") || hasStart(col, "0;")) - col.erase(0, 2); - - const int n = std::stoi(col); - if ((n >= 100 && n <= 107) || (n >= 40 && n <= 47)) - type = "background-color"; -#endif // !ANDROID_APP - - // last number - // clang-format off - switch (col.back()) - { - case '0': col = colors.gui_black; break; - case '1': col = colors.gui_red; break; - case '2': col = colors.gui_green; break; - case '3': col = colors.gui_yellow; break; - case '4': col = colors.gui_blue; break; - case '5': col = colors.gui_magenta; break; - case '6': col = colors.gui_cyan; break; - case '7': col = colors.gui_white; break; - } - - if (col.at(0) != '#') - col.erase(0, col.find('#')); - - return { col, weight, type }; - // clang-format on -} - -static std::string convert_ansi_escape_rgb(const std::string_view noesc_str) -{ - if (std::count(noesc_str.begin(), noesc_str.end(), ';') < 4) - die(_("ANSI escape code color '\\e[{}' should have an rgb type value\n" - "e.g \\e[38;2;255;255;255m"), - noesc_str); - if (noesc_str.rfind('m') == std::string::npos) - die(_("Parser: failed to parse layout/ascii art: missing 'm' while using ANSI color escape code in '\\e[{}'"), - noesc_str); - - const std::vector<std::string>& rgb_str = split(noesc_str.substr(5), ';'); - - const uint r = std::stoul(rgb_str.at(0)); - const uint g = std::stoul(rgb_str.at(1)); - const uint b = std::stoul(rgb_str.at(2)); - const uint result = (r << 16) | (g << 8) | (b); - - std::stringstream ss; - ss << std::hex << result; - return ss.str(); -} - -std::string parse(const std::string& input, std::string& _, parse_args_t& parse_args) -{ - return parse(input, parse_args.systemInfo, _, parse_args.layout, parse_args.tmp_layout, - parse_args.config, parse_args.colors, parse_args.parsingLayout, parse_args.no_more_reset); -} - -std::string parse(const std::string& input, parse_args_t& parse_args) -{ - return parse(input, parse_args.systemInfo, parse_args.pureOutput, parse_args.layout, parse_args.tmp_layout, - parse_args.config, parse_args.colors, parse_args.parsingLayout, parse_args.no_more_reset); -} - -std::string get_and_color_percentage(const float& n1, const float& n2, parse_args_t& parse_args, - const bool invert) -{ - const Config& config = parse_args.config; - const float result = n1 / n2 * static_cast<float>(100); - - std::string color; - if (!invert) - { - if (result <= 45) - color = "${" + config.percentage_colors.at(0) + "}"; - else if (result <= 80) - color = "${" + config.percentage_colors.at(1) + "}"; - else - color = "${" + config.percentage_colors.at(2) + "}"; - } - else -{ - if (result <= 45) - color = "${" + config.percentage_colors.at(2) + "}"; - else if (result <= 80) - color = "${" + config.percentage_colors.at(1) + "}"; - else - color = "${" + config.percentage_colors.at(0) + "}"; - } - - return parse(fmt::format("{}{:.2f}%${{0}}", color, result), _, parse_args); -} - -std::string getInfoFromName(const systemInfo_t& systemInfo, const std::string_view moduleName, - const std::string_view moduleMemberName) -{ - if (const auto& it1 = systemInfo.find(moduleName.data()); it1 != systemInfo.end()) - { - if (const auto& it2 = it1->second.find(moduleMemberName.data()); it2 != it1->second.end()) - { - const variant& result = it2->second; - - if (std::holds_alternative<std::string>(result)) - return std::get<std::string>(result); - - else if (std::holds_alternative<double>(result)) - return fmt::format("{:.2f}", (std::get<double>(result))); - - else - return fmt::to_string(std::get<size_t>(result)); - } - } - - return "(unknown/invalid module)"; -} - -std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate = true, const char until = 0); - -std::optional<std::string> parse_conditional_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('[')) - return {}; - - const std::string& condA = parse(parser, parse_args, evaluate, ','); - const std::string& condB = parse(parser, parse_args, evaluate, ','); - - const bool cond = (condA == condB); - - const std::string& condTrue = parse(parser, parse_args, cond, ','); - const std::string& condFalse = parse(parser, parse_args, !cond, ']'); - - return cond ? condTrue : condFalse; -} - -std::optional<std::string> parse_command_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('(')) - return {}; - - std::string command = parse(parser, parse_args, evaluate, ')'); - - if (!evaluate) - return {}; - - const bool removetag = (command.front() == '!'); - if (removetag) - command.erase(0, 1); - - const std::string& cmd_output = read_shell_exec(command); - if (!parse_args.parsingLayout && !removetag && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, command.length() + "$()"_len, cmd_output); - - return cmd_output; -} - -std::optional<std::string> parse_color_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('{')) - return {}; - - std::string color = parse(parser, parse_args, evaluate, '}'); - - if (!evaluate) - return {}; - - std::string output; - const Config& config = parse_args.config; - const colors_t& colors = parse_args.colors; - const size_t taglen = color.length() + "${}"_len; - -#if ANDROID_APP - std::string& endspan = parse_args.endspan; - output += endspan; - endspan.clear(); - - const auto& append_endspan = [&endspan](const std::string_view tag) { - endspan += "</"; - endspan += tag; - endspan += ">"; - }; -#else - const std::string endspan {!parse_args.firstrun_clr ? "</span>" : ""}; -#endif - - if (config.m_disable_colors) - { - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return ""; - } - - // if at end there a '$', it will make the end output "$</span>" and so it will confuse - // addValueFromModule() and so let's make it "$ </span>". this is geniunenly stupid - if (config.gui && output.back() == '$') - output += ' '; - - if (!config.colors_name.empty()) - { - const auto& it_name = std::find(config.colors_name.begin(), config.colors_name.end(), color); - if (it_name != config.colors_name.end()) - { - const auto& it_value = std::distance(config.colors_name.begin(), it_name); - - if (hasStart(color, "auto")) - { - // "ehhmmm why goto and double code? that's ugly and unconvienient :nerd:" - // I don't care, it does the work and well - if (color == *it_name) - color = config.colors_value.at(it_value); - goto jumpauto; - } - - if (color == *it_name) - color = config.colors_value.at(it_value); - } - } - - if (hasStart(color, "auto")) - { - int ver = color.length() > 4 ? std::stoi(color.substr(4)) - 1 : 0; - if (ver < 1 || static_cast<size_t>(ver) >= auto_colors.size()) - ver = 0; - - if (auto_colors.empty()) - auto_colors.push_back(NOCOLOR_BOLD); - - color = auto_colors.at(ver); - } - -jumpauto: -#if !ANDROID_APP - if (color == "1") - { - output += config.gui ? endspan + "<span weight='bold'>" : NOCOLOR_BOLD; - } - else if (color == "0") - { - output += config.gui ? endspan + "<span>" : NOCOLOR; - } - else - { - std::string str_clr; - if (config.gui) - { - switch (fnv1a16::hash(color)) - { - case "black"_fnv1a16: str_clr = colors.gui_black; break; - case "red"_fnv1a16: str_clr = colors.gui_red; break; - case "blue"_fnv1a16: str_clr = colors.gui_blue; break; - case "green"_fnv1a16: str_clr = colors.gui_green; break; - case "cyan"_fnv1a16: str_clr = colors.gui_cyan; break; - case "yellow"_fnv1a16: str_clr = colors.gui_yellow; break; - case "magenta"_fnv1a16: str_clr = colors.gui_magenta; break; - case "white"_fnv1a16: str_clr = colors.gui_white; break; - default: str_clr = color; break; - } - - const size_t pos = str_clr.rfind('#'); - if (pos != std::string::npos) - { - std::string tagfmt = "span "; - const std::string& opt_clr = str_clr.substr(0, pos); - - size_t argmode_pos = 0; - const auto& append_argmode = [&](const std::string_view fmt, const std::string_view mode) -> size_t { - if (opt_clr.at(argmode_pos + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', argmode_pos); - if (closebrak == std::string::npos) - die(_("'{}' mode in color '{}' doesn't have close bracket"), mode, str_clr); - - const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); - tagfmt += fmt.data() + value + "' "; - - return closebrak; - } - return 0; - }; - - bool bgcolor = false; - for (size_t i = 0; i < opt_clr.length(); ++i) - { - switch (opt_clr.at(i)) - { - case 'b': - bgcolor = true; - tagfmt += "bgcolor='" + str_clr.substr(pos) + "' "; - break; - case '!': tagfmt += "weight='bold' "; break; - case 'u': tagfmt += "underline='single' "; break; - case 'i': tagfmt += "style='italic' "; break; - case 'o': tagfmt += "overline='single' "; break; - case 's': tagfmt += "strikethrough='true' "; break; - - case 'a': - argmode_pos = i; - i += append_argmode("fgalpha='", "fgalpha"); - break; - - case 'A': - argmode_pos = i; - i += append_argmode("bgalpha='", "bgalpha"); - break; - - case 'L': - argmode_pos = i; - i += append_argmode("underline='", "underline option"); - break; - - case 'U': - argmode_pos = i; - i += append_argmode("underline_color='", "colored underline"); - break; - - case 'B': - argmode_pos = i; - i += append_argmode("bgcolor='", "bgcolor"); - break; - - case 'w': - argmode_pos = i; - i += append_argmode("weight='", "font weight style"); - break; - - case 'O': - argmode_pos = i; - i += append_argmode("overline_color='", "overline color"); - break; - - case 'S': - argmode_pos = i; - i += append_argmode("strikethrough_color='", "color of strikethrough line"); - break; - } - } - - if (!bgcolor) - tagfmt += "fgcolor='" + str_clr.substr(pos) + "' "; - - tagfmt.pop_back(); - output += endspan + "<" + tagfmt + ">"; - } - - // "\\e" is for checking in the ascii_art, \033 in the config - else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) - { - const std::string& noesc_str = hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); - debug("noesc_str = {}", noesc_str); - - if (hasStart(noesc_str, "38;2;") || hasStart(noesc_str, "48;2;")) - { - const std::string& hexclr = convert_ansi_escape_rgb(noesc_str); - output += - fmt::format("{}<span {}gcolor='#{}'>", endspan, hasStart(noesc_str, "38") ? 'f' : 'b', hexclr); - } - else if (hasStart(noesc_str, "38;5;") || hasStart(noesc_str, "48;5;")) - { - die(_("256 true color '{}' works only in terminal"), noesc_str); - } - else - { - const std::array<std::string, 3>& clrs = get_ansi_color(noesc_str, colors); - const std::string_view color = clrs.at(0); - const std::string_view weight = clrs.at(1); - const std::string_view type = clrs.at(2); - output += fmt::format("{}<span {}='{}' weight='{}'>", endspan, type, color, weight); - } - } - - else - { - error(_("PARSER: failed to parse line with color '{}'"), str_clr); - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return output; - } - } - // if (!config.gui) - else - { - switch (fnv1a16::hash(color)) - { - case "black"_fnv1a16: str_clr = colors.black; break; - case "red"_fnv1a16: str_clr = colors.red; break; - case "blue"_fnv1a16: str_clr = colors.blue; break; - case "green"_fnv1a16: str_clr = colors.green; break; - case "cyan"_fnv1a16: str_clr = colors.cyan; break; - case "yellow"_fnv1a16: str_clr = colors.yellow; break; - case "magenta"_fnv1a16: str_clr = colors.magenta; break; - case "white"_fnv1a16: str_clr = colors.white; break; - default: str_clr = color; break; - } - - const size_t pos = str_clr.rfind('#'); - if (pos != std::string::npos) - { - const std::string& opt_clr = str_clr.substr(0, pos); - - fmt::text_style style; - - const auto& skip_gui_argmode = [&opt_clr](const size_t index) -> size_t { - if (opt_clr.at(index + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', index); - if (closebrak == std::string::npos) - return 0; - - return closebrak; - } - return 0; - }; - - bool bgcolor = false; - for (size_t i = 0; i < opt_clr.length(); ++i) - { - switch (opt_clr.at(i)) - { - case 'b': - bgcolor = true; - append_styles(style, fmt::bg(hexStringToColor(str_clr.substr(pos)))); - break; - case '!': append_styles(style, fmt::emphasis::bold); break; - case 'u': append_styles(style, fmt::emphasis::underline); break; - case 'i': append_styles(style, fmt::emphasis::italic); break; - case 'l': append_styles(style, fmt::emphasis::blink); break; - case 's': append_styles(style, fmt::emphasis::strikethrough); break; - - case 'U': - case 'B': - case 'S': - case 'a': - case 'w': - case 'O': - case 'A': - case 'L': i += skip_gui_argmode(i); break; - } - } - - if (!bgcolor) - append_styles(style, fmt::fg(hexStringToColor(str_clr.substr(pos)))); - - // you can't fmt::format(style, ""); ughh - if (style.has_emphasis()) - { - fmt::detail::ansi_color_escape<char> emph(style.get_emphasis()); - output += emph.begin(); - } - if (style.has_background() || style.has_foreground()) - { - const uint32_t rgb_num = bgcolor ? style.get_background().value.rgb_color : style.get_foreground().value.rgb_color; - fmt::rgb rgb(rgb_num); - fmt::detail::ansi_color_escape<char> ansi(rgb, bgcolor ? "\x1B[48;2;" : "\x1B[38;2;"); - output += ansi.begin(); - } - } - - // "\\e" is for checking in the ascii_art, \033 in the config - else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) - { - output += "\033["; - output += hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); - } - - else - { - error(_("PARSER: failed to parse line with color '{}'"), str_clr); - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return output; - } - } - if (!parse_args.parsingLayout && - std::find(auto_colors.begin(), auto_colors.end(), color) == auto_colors.end()) - auto_colors.push_back(color); - } -#else - if (color == "1") - { - output += "<b>"; - append_endspan("b"); - } - else if (color == "0") - { - output += "<span>"; - append_endspan("span"); - } - else - { - std::string str_clr; - switch (fnv1a16::hash(color)) - { - case "black"_fnv1a16: str_clr = colors.gui_black; break; - case "red"_fnv1a16: str_clr = colors.gui_red; break; - case "blue"_fnv1a16: str_clr = colors.gui_blue; break; - case "green"_fnv1a16: str_clr = colors.gui_green; break; - case "cyan"_fnv1a16: str_clr = colors.gui_cyan; break; - case "yellow"_fnv1a16: str_clr = colors.gui_yellow; break; - case "magenta"_fnv1a16: str_clr = colors.gui_magenta; break; - case "white"_fnv1a16: str_clr = colors.gui_white; break; - default: str_clr = color; break; - } - - const size_t pos = str_clr.rfind('#'); - if (pos != std::string::npos) - { - std::string tagfmt; - const std::string& opt_clr = str_clr.substr(0, pos); - const std::string& hexclr = str_clr.substr(pos + 1); - - size_t argmode_pos = 0; - /*const auto& get_argmode_value = [&](const std::string_view error) -> std::string { - if (opt_clr.at(argmode_pos + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', argmode_pos); - if (closebrak == std::string::npos) - die(_("{} mode in tag color {} doesn't have close bracket"), error, str_clr); - - const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); - return value; - } - return ""; - };*/ - - const auto& append_argmode = [&](const std::string_view fmt, const std::string_view error) -> size_t { - if (opt_clr.at(argmode_pos + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', argmode_pos); - if (closebrak == std::string::npos) - die(_("{} mode in tag color {} doesn't have close bracket"), error, str_clr); - - const std::string& value = opt_clr.substr(argmode_pos + 2, closebrak - argmode_pos - 2); - tagfmt += fmt.data() + value + ";\">"; - - return closebrak; - } - return 0; - }; - - const auto& skip_gui_argmode = [&](const size_t index) -> size_t { - if (opt_clr.at(index + 1) == '(') - { - const size_t closebrak = opt_clr.find(')', index); - if (closebrak == std::string::npos) - return 0; - - return closebrak; - } - return 0; - }; - - bool bgcolor = false; - for (size_t i = 0; i < opt_clr.length(); ++i) - { - switch (opt_clr.at(i)) - { - case 'b': - bgcolor = true; - tagfmt += "<span style=\"background-color:" + str_clr.substr(pos) + ";\">"; - break; - case '!': tagfmt += "<b>"; append_endspan("b"); break; - case 'u': tagfmt += "<u>"; append_endspan("u"); break; - case 'i': tagfmt += "<i>"; append_endspan("i"); break; - case 's': tagfmt += "<s>"; append_endspan("s"); break; - - case 'B': - argmode_pos = i; - i += append_argmode("<span style=\"background-color:", "bgcolor"); - append_endspan("font"); - break; - - case 'S': - argmode_pos = i; - i += append_argmode("<span style=\"color:", "color of strikethrough line"); - tagfmt += "<s>"; - append_endspan("span"); - append_endspan("s"); - break; - - case 'a': - case 'A': - // alpha setting doesn't seem to work correctly no matter what. disabling for now. - // if someone could help I would really really appreciate it - /*{ - argmode_pos = i; - std::string alpha_s = get_argmode_value("f/bgcolor alpha"); - std::uint16_t alpha = 0; - const size_t perc_pos = alpha_s.find('%'); - if (perc_pos != alpha_s.npos) - { - alpha_s.erase(perc_pos); - alpha = (std::stoi(alpha_s) * 0xf / 100); - } - else - { - alpha = std::stoi(alpha_s); - } - - std::stringstream ss; - ss << std::hex << alpha; - if (hexclr.length() <= 6) - str_clr.insert(pos + 1, ss.str()); - else - str_clr.replace(pos + 1, 2, ss.str()); - - i += skip_gui_argmode(i); - break; - }*/ - - case 'L': - case 'U': - case 'w': - case 'O': - case 'o': i += skip_gui_argmode(i); - } - } - - if (!bgcolor) - tagfmt += "<span style=\"color:" + str_clr.substr(pos) + ";\">"; - - append_endspan("span"); - output += tagfmt; - } - - // "\\e" is for checking in the ascii_art, \033 in the config - else if (hasStart(str_clr, "\\e") || hasStart(str_clr, "\033")) - { - const std::string& noesc_str = hasStart(str_clr, "\033") ? str_clr.substr(2) : str_clr.substr(3); - debug("noesc_str = {}", noesc_str); - - if (hasStart(noesc_str, "38;2;") || hasStart(noesc_str, "48;2;")) - { - const std::string& hexclr = convert_ansi_escape_rgb(noesc_str); - output += fmt::format("<span style=\"{}color:#{};\">", hasStart(noesc_str, "48") ? "background-" : "", - hexclr); - } - else if (hasStart(noesc_str, "38;5;") || hasStart(noesc_str, "48;5;")) - { - die(_("256 true color '{}' works only in terminal"), noesc_str); - } - else - { - const std::array<std::string, 3>& clrs = get_ansi_color(noesc_str, colors); - const std::string_view color = clrs.at(0); - const std::string_view weight = clrs.at(1); - const std::string_view type = clrs.at(2); - output += fmt::format("{}<span style=\"{}:{};\">", weight, type, color); - - if (weight == "<b>") - append_endspan("b"); - } - append_endspan("span"); - } - - else - { - error(_("PARSER: failed to parse line with color '{}'"), str_clr); - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - return output; - } - - if (!parse_args.parsingLayout && - std::find(auto_colors.begin(), auto_colors.end(), color) == auto_colors.end()) - auto_colors.push_back(color); - } -#endif // !ANDROID_APP - - if (!parse_args.parsingLayout && parser.dollar_pos != std::string::npos) - parse_args.pureOutput.erase(parser.dollar_pos, taglen); - - parse_args.firstrun_clr = false; - - return output; -} - -std::optional<std::string> parse_info_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('<')) - return {}; - - const std::string& module = parse(parser, parse_args, evaluate, '>'); - - if (!evaluate) - return {}; - - const size_t dot_pos = module.find('.'); - if (dot_pos == module.npos) - { - addValueFromModule(module, parse_args); - const std::string& info = getInfoFromName(parse_args.systemInfo, module, "module-" + module); - - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); - - return info; - } - - const std::string& moduleName = module.substr(0, dot_pos); - const std::string& moduleMemberName = module.substr(dot_pos + 1); - addValueFromModuleMember(moduleName, moduleMemberName, parse_args); - - const std::string& info = getInfoFromName(parse_args.systemInfo, moduleName, moduleMemberName); - - if (parser.dollar_pos != std::string::npos) - parse_args.pureOutput.replace(parser.dollar_pos, module.length() + "$<>"_len, info); - return info; -} - -std::optional<std::string> parse_perc_tag(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('%')) - return {}; - - const std::string& command = parse(parser, parse_args, evaluate, '%'); - - if (!evaluate) - return {}; - - const size_t comma_pos = command.find(','); - if (comma_pos == std::string::npos) - die(_("percentage tag '{}' doesn't have a comma for separating the 2 numbers"), command); - - const bool invert = (command.front() == '!'); - - const float n1 = std::stof(parse(command.substr(invert ? 1 : 0, comma_pos), _, parse_args)); - const float n2 = std::stof(parse(command.substr(comma_pos + 1), _, parse_args)); - - return get_and_color_percentage(n1, n2, parse_args, invert); -} - -std::optional<std::string> parse_tags(Parser& parser, parse_args_t& parse_args, const bool evaluate) -{ - if (!parser.try_read('$')) - return {}; - - if (parser.dollar_pos != std::string::npos) - parser.dollar_pos = parser.pureOutput.find('$', parser.dollar_pos); - - if (const auto& color_tag = parse_color_tag(parser, parse_args, evaluate)) - return color_tag; - - if (const auto& module_tag = parse_info_tag(parser, parse_args, evaluate)) - return module_tag; - - if (const auto& command_tag = parse_command_tag(parser, parse_args, evaluate)) - return command_tag; - - if (const auto& ifTag = parse_conditional_tag(parser, parse_args, evaluate)) - return ifTag; - - if (const auto& perc_tag = parse_perc_tag(parser, parse_args, evaluate)) - return perc_tag; - - parser.rewind(); - return {}; -} - -std::string parse(Parser& parser, parse_args_t& parse_args, const bool evaluate, const char until) -{ - std::string result; - - while (until == 0 ? !parser.is_eof() : !parser.try_read(until)) - { - if (until != 0 && parser.is_eof()) - { - error(_("PARSER: Missing tag close bracket {} in string '{}'"), until, parser.src); - return result; - } - - if (parser.try_read('\\')) - { - result += parser.read_char(until == 0); - } - else if (const auto& tagStr = parse_tags(parser, parse_args, evaluate)) - { - result += *tagStr; - } - else - { - result += parser.read_char(until == 0); - } - } - - return result; -} - -std::string parse(std::string input, systemInfo_t& systemInfo, std::string& pureOutput, std::vector<std::string>& layout, - std::vector<std::string>& tmp_layout, const Config& config, const colors_t& colors, const bool parsingLayout, bool& no_more_reset) -{ - if (!config.sep_reset.empty() && parsingLayout && !no_more_reset) - { - if (config.sep_reset_after) - replace_str(input, config.sep_reset, config.sep_reset + "${0}"); - else - replace_str(input, config.sep_reset, "${0}" + config.sep_reset); - - no_more_reset = true; - } - - // escape pango markup - // https://gitlab.gnome.org/GNOME/glib/-/blob/main/glib/gmarkup.c#L2150 - // workaround: just put "\<" or "\&" in the config, e.g "$<os.kernel> \<- Kernel" - if (config.gui) - { - replace_str(input, "\\<", "<"); - replace_str(input, "\\&", "&"); - } - else - { - replace_str(input, "\\<", "<"); - replace_str(input, "\\&", "&"); - } -#if ANDROID_APP - if (!parsingLayout) - replace_str(input, " ", " "); -#endif - - parse_args_t parse_args{ systemInfo, pureOutput, layout, tmp_layout, config, colors, parsingLayout, true, no_more_reset, "" }; - Parser parser{ input, pureOutput }; - - std::string ret{ parse(parser, parse_args) }; - -#if ANDROID_APP - if (config.gui && !parse_args.firstrun_clr) - ret += parse_args.endspan; -#else - if (config.gui && !parse_args.firstrun_clr) - ret += "</span>"; -#endif - - replace_str(parse_args.pureOutput, " ", " "); - - return ret; -} - -static std::string get_auto_uptime(const std::uint16_t days, const std::uint16_t hours, const std::uint16_t mins, - const std::uint16_t secs, const Config& config) -{ - if (days == 0 && hours == 0 && mins == 0) - return fmt::format("{}{}", secs, config.uptime_s_fmt); - - std::string ret; - - if (days > 0) - ret += fmt::format("{}{}, ", days, config.uptime_d_fmt); - - if (hours > 0) - ret += fmt::format("{}{}, ", hours, config.uptime_h_fmt); - - if (mins > 0) - ret += fmt::format("{}{}, ", mins, config.uptime_m_fmt); - - ret.erase(ret.length() - 2); // the last ", " - - return ret; -} - -static std::string get_auto_gtk_format(const std::string_view gtk2, const std::string_view gtk3, - const std::string_view gtk4) -{ - if ((gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE)) - { - if (gtk2 == gtk3 && gtk2 == gtk4) - return fmt::format("{} [GTK2/3/4]", gtk4); - else if (gtk2 == gtk3) - return fmt::format("{} [GTK2/3], {} [GTK4]", gtk2, gtk4); - else if (gtk4 == gtk3) - return fmt::format("{} [GTK2], {} [GTK3/4]", gtk2, gtk4); - else - return fmt::format("{} [GTK2], {} [GTK3], {} [GTK4]", gtk2, gtk3, gtk4); - } - - else if (gtk3 != MAGIC_LINE && gtk4 != MAGIC_LINE) - { - if (gtk3 == gtk4) - return fmt::format("{} [GTK3/4]", gtk4); - else - return fmt::format("{} [GTK3], {} [GTK4]", gtk3, gtk4); - } - - else if (gtk2 != MAGIC_LINE && gtk3 != MAGIC_LINE) - { - if (gtk2 == gtk3) - return fmt::format("{} [GTK2/3]", gtk3); - else - return fmt::format("{} [GTK2], {} [GTK3]", gtk2, gtk3); - } - - else if (gtk4 != MAGIC_LINE) - return fmt::format("{} [GTK4]", gtk4); - else if (gtk3 != MAGIC_LINE) - return fmt::format("{} [GTK3]", gtk3); - else if (gtk2 != MAGIC_LINE) - return fmt::format("{} [GTK2]", gtk2); - - return MAGIC_LINE; -} - -static std::string prettify_term_name(const std::string_view term_name) -{ - switch (fnv1a16::hash(str_tolower(term_name.data()))) - { - case "gnome-terminal"_fnv1a16: - case "gnome terminal"_fnv1a16: return "GNOME Terminal"; - - case "gnome-console"_fnv1a16: - case "gnome console"_fnv1a16: return "GNOME console"; - } - return term_name.data(); -} - -static std::string prettify_de_name(const std::string_view de_name) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "kde"_fnv1a16: - case "plasma"_fnv1a16: - case "plasmashell"_fnv1a16: - case "plasmawayland"_fnv1a16: return "KDE Plasma"; - - case "gnome"_fnv1a16: - case "gnome-shell"_fnv1a16: return "GNOME"; - - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - case "xfce4-session"_fnv1a16: return "Xfce4"; - - case "mate"_fnv1a16: - case "mate-session"_fnv1a16: return "Mate"; - - case "lxqt"_fnv1a16: - case "lxqt-session"_fnv1a16: return "LXQt"; - } - - return de_name.data(); -} - -systemInfo_t queried_gpus; -systemInfo_t queried_disks; -systemInfo_t queried_themes_names; -systemInfo_t queried_themes; - -// clang-format on -void addValueFromModuleMember(const std::string& moduleName, const std::string& moduleMemberName, - parse_args_t& parse_args) -{ -#define SYSINFO_INSERT(x) sysInfo.at(moduleName).insert({ moduleMemberName, variant(x) }) - - // just aliases for convention - const Config& config = parse_args.config; - systemInfo_t& sysInfo = parse_args.systemInfo; - - const auto& moduleMember_hash = fnv1a16::hash(moduleMemberName); - const std::uint16_t byte_unit = config.use_SI_unit ? 1000 : 1024; - constexpr std::array<std::string_view, 32> sorted_valid_prefixes = { "B", "EB", "EiB", "GB", "GiB", "kB", - "KiB", "MB", "MiB", "PB", "PiB", "TB", - "TiB", "YB", "YiB", "ZB", "ZiB" }; - - const auto& return_devided_bytes = [&sorted_valid_prefixes, &moduleMemberName](const double& amount) -> double { - const std::string& prefix = moduleMemberName.substr(moduleMemberName.find('-') + 1); - if (std::binary_search(sorted_valid_prefixes.begin(), sorted_valid_prefixes.end(), prefix)) - return devide_bytes(amount, prefix).num_bytes; - - return 0; - }; - - if (moduleName == "os") - { - Query::System query_system; - - const std::chrono::seconds uptime_secs(query_system.uptime()); - const std::chrono::minutes& uptime_mins = std::chrono::duration_cast<std::chrono::minutes>(uptime_secs); - const std::chrono::hours& uptime_hours = std::chrono::duration_cast<std::chrono::hours>(uptime_secs); - - // let's support a little of C++17 without any `#if __cpluscplus` stuff - const std::uint16_t uptime_days = uptime_secs.count() / (60 * 60 * 24); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_system.os_pretty_name()); break; - case "name_id"_fnv1a16: SYSINFO_INSERT(query_system.os_id()); break; - - case "uptime"_fnv1a16: - SYSINFO_INSERT(get_auto_uptime(uptime_days, uptime_hours.count() % 24, uptime_mins.count() % 60, - uptime_secs.count() % 60, config)); - break; - - case "uptime_secs"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_secs.count() % 60)); break; - case "uptime_mins"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_mins.count() % 60)); break; - case "uptime_hours"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_hours.count()) % 24); break; - case "uptime_days"_fnv1a16: SYSINFO_INSERT(static_cast<size_t>(uptime_days)); break; - - case "kernel"_fnv1a16: - SYSINFO_INSERT(query_system.kernel_name() + ' ' + query_system.kernel_version()); - break; - - case "kernel_name"_fnv1a16: SYSINFO_INSERT(query_system.kernel_name()); break; - case "kernel_version"_fnv1a16: SYSINFO_INSERT(query_system.kernel_version()); break; - case "packages"_fnv1a16: - case "pkgs"_fnv1a16: SYSINFO_INSERT(query_system.pkgs_installed(config)); break; - case "initsys_name"_fnv1a16: SYSINFO_INSERT(query_system.os_initsys_name()); break; - case "initsys_version"_fnv1a16: SYSINFO_INSERT(query_system.os_initsys_version()); break; - case "hostname"_fnv1a16: SYSINFO_INSERT(query_system.hostname()); break; - case "version_codename"_fnv1a16: SYSINFO_INSERT(query_system.os_version_codename()); break; - case "version_id"_fnv1a16: SYSINFO_INSERT(query_system.os_versionid()); break; - } - } - } - - else if (moduleName == "system") - { - Query::System query_system; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "host"_fnv1a16: - SYSINFO_INSERT(query_system.host_vendor() + ' ' + query_system.host_modelname() + ' ' + - query_system.host_version()); - break; - - case "host_name"_fnv1a16: SYSINFO_INSERT(query_system.host_modelname()); break; - case "host_vendor"_fnv1a16: SYSINFO_INSERT(query_system.host_vendor()); break; - case "host_version"_fnv1a16: SYSINFO_INSERT(query_system.host_version()); break; - case "arch"_fnv1a16: SYSINFO_INSERT(query_system.arch()); break; - } - } - } - - // clang-format on - else if (moduleName == "user") - { - Query::User query_user; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_user.name()); break; - - case "shell"_fnv1a16: - SYSINFO_INSERT(query_user.shell_name() + ' ' + query_user.shell_version(query_user.shell_name())); - break; - - case "shell_name"_fnv1a16: SYSINFO_INSERT(query_user.shell_name()); break; - case "shell_path"_fnv1a16: SYSINFO_INSERT(query_user.shell_path()); break; - case "shell_version"_fnv1a16: SYSINFO_INSERT(query_user.shell_version(query_user.shell_name())); break; - - case "de_name"_fnv1a16: - SYSINFO_INSERT(prettify_de_name( - query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), - query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())))); - break; - - case "de_version"_fnv1a16: - SYSINFO_INSERT(query_user.de_version( - query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), - query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())))); - break; - - case "wm_name"_fnv1a16: - SYSINFO_INSERT(query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name())); - break; - - case "wm_version"_fnv1a16: - SYSINFO_INSERT(query_user.wm_version(query_user.m_bDont_query_dewm, query_user.term_name())); - break; - - case "terminal"_fnv1a16: - SYSINFO_INSERT(prettify_term_name(query_user.term_name()) + ' ' + - query_user.term_version(query_user.term_name())); - break; - - case "terminal_name"_fnv1a16: SYSINFO_INSERT(prettify_term_name(query_user.term_name())); break; - case "terminal_version"_fnv1a16: SYSINFO_INSERT(query_user.term_version(query_user.term_name())); break; - } - } - } - - else if (moduleName == "theme") - { - Query::Theme query_theme(queried_themes, config, false); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "cursor"_fnv1a16: - if (query_theme.cursor_size() == UNKNOWN) - SYSINFO_INSERT(query_theme.cursor()); - else - SYSINFO_INSERT(fmt::format("{} ({}px)", query_theme.cursor(), query_theme.cursor_size())); - break; - - case "cursor_name"_fnv1a16: SYSINFO_INSERT(query_theme.cursor()); break; - case "cursor_size"_fnv1a16: SYSINFO_INSERT(query_theme.cursor_size()); break; - } - } - } - - else if (moduleName == "theme-gsettings") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (hasStart(moduleMemberName, "cursor")) - { - Query::Theme query_cursor(queried_themes, config, true); - switch (moduleMember_hash) - { - case "cursor"_fnv1a16: - if (query_cursor.cursor_size() == UNKNOWN) - SYSINFO_INSERT(query_cursor.cursor()); - else - SYSINFO_INSERT(fmt::format("{} ({}px)", query_cursor.cursor(), query_cursor.cursor_size())); - break; - case "cursor_name"_fnv1a16: SYSINFO_INSERT(query_cursor.cursor()); break; - case "cursor_size"_fnv1a16: SYSINFO_INSERT(query_cursor.cursor_size()); break; - } - } - else - { - Query::Theme query_theme(0, queried_themes, "gsettings", config, true); - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_theme()); break; - case "icons"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_icon_theme()); break; - case "font"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_font()); break; - } - } - } - } - - // clang-format off - else if (moduleName == "theme-gtk-all") - { - Query::Theme gtk2(2, queried_themes, "gtk2", config); - Query::Theme gtk3(3, queried_themes, "gtk3", config); - Query::Theme gtk4(4, queried_themes, "gtk4", config); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_theme(), gtk3.gtk_theme(), gtk4.gtk_theme())); break; - case "icons"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_icon_theme(), gtk3.gtk_icon_theme(), gtk4.gtk_icon_theme())); break; - case "font"_fnv1a16: SYSINFO_INSERT(get_auto_gtk_format(gtk2.gtk_font(), gtk3.gtk_font(), gtk4.gtk_font())); break; - } - } - } - - else if (hasStart(moduleName, "theme-gtk")) - { - const std::uint8_t ver = - static_cast<std::uint8_t>(moduleName.length() > 9 ? std::stoi(moduleName.substr(9)) : 0); - - if (ver <= 0) - die(_("seems theme-gtk module name '{}' doesn't have a version number to query.\n" - "Syntax should be like 'theme_gtkN' which N stands for the version of gtk to query (single number)"), - moduleName); - - Query::Theme query_theme(ver, queried_themes, fmt::format("gtk{}", ver), config); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_theme()); break; - case "icons"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_icon_theme()); break; - case "font"_fnv1a16: SYSINFO_INSERT(query_theme.gtk_font()); break; - } - } - } - - // clang-format on - else if (moduleName == "cpu") - { - Query::CPU query_cpu; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_cpu.name()); break; - case "nproc"_fnv1a16: SYSINFO_INSERT(query_cpu.nproc()); break; - case "freq_cur"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_cur()); break; - case "freq_max"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_max()); break; - case "freq_min"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_min()); break; - case "freq_bios_limit"_fnv1a16: SYSINFO_INSERT(query_cpu.freq_bios_limit()); break; - - case "temp_C"_fnv1a16: SYSINFO_INSERT(query_cpu.temp()); break; - case "temp_F"_fnv1a16: SYSINFO_INSERT(query_cpu.temp() * 1.8 + 34); break; - case "temp_K"_fnv1a16: SYSINFO_INSERT(query_cpu.temp() + 273.15); break; - } - } - } - - else if (hasStart(moduleName, "gpu")) - { - const std::string& id = moduleName.length() > 3 ? moduleName.substr(3) : "0"; - - Query::GPU query_gpu(id, queried_gpus); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "name"_fnv1a16: SYSINFO_INSERT(query_gpu.name()); break; - case "vendor"_fnv1a16: SYSINFO_INSERT(shorten_vendor_name(query_gpu.vendor())); break; - case "vendor_long"_fnv1a16: SYSINFO_INSERT(query_gpu.vendor()); break; - } - } - } - - else if (hasStart(moduleName, "disk")) - { - if (moduleName.length() < "disk()"_len) - die(_("invalid disk module name '{}', must be disk(/path/to/fs) e.g: disk(/)"), moduleName); - - enum - { - USED = 0, - TOTAL, - FREE - }; - std::string path{ moduleName.data() }; - path.erase(0, 5); // disk( - path.pop_back(); // ) - debug("disk path = {}", path); - - Query::Disk query_disk(path, queried_disks, parse_args); - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - byte_units.at(TOTAL) = auto_devide_bytes(query_disk.total_amount(), byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_disk.used_amount(), byte_unit); - byte_units.at(FREE) = auto_devide_bytes(query_disk.free_amount(), byte_unit); - - switch (moduleMember_hash) - { - case "fs"_fnv1a16: SYSINFO_INSERT(query_disk.typefs()); break; - case "device"_fnv1a16: SYSINFO_INSERT(query_disk.device()); break; - case "mountdir"_fnv1a16: SYSINFO_INSERT(query_disk.mountdir()); break; - - case "types"_fnv1a16: - { - std::string str; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_EXTERNAL) - str += "External, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_HIDDEN) - str += "Hidden, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_READ_ONLY) - str += "Read-only, "; - - if (!str.empty()) - str.erase(str.length() - 2); - SYSINFO_INSERT(str); - - } break; - - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_disk.free_amount(), query_disk.total_amount(), - parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_disk.used_amount(), query_disk.total_amount(), parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_disk.total_amount())); - } - } - } - - else if (moduleName == "swap") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - FREE, - }; - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the diviser - byte_units.at(FREE) = auto_devide_bytes(query_ram.swap_free_amount() * byte_unit, byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_ram.swap_used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.swap_total_amount() * byte_unit, byte_unit); - - switch (moduleMember_hash) - { - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_ram.swap_free_amount(), query_ram.swap_total_amount(), - parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_ram.swap_used_amount(), query_ram.swap_total_amount(), - parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.swap_total_amount())); - } - } - } - - else if (moduleName == "ram") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - FREE, - }; - std::array<byte_units_t, 3> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the diviser - byte_units.at(USED) = auto_devide_bytes(query_ram.used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.total_amount() * byte_unit, byte_unit); - byte_units.at(FREE) = auto_devide_bytes(query_ram.free_amount() * byte_unit, byte_unit); - - switch (moduleMember_hash) - { - case "used"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(USED).num_bytes, byte_units.at(USED).unit)); - break; - - case "total"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(TOTAL).num_bytes, byte_units.at(TOTAL).unit)); - break; - - case "free"_fnv1a16: - SYSINFO_INSERT(fmt::format("{:.2f} {}", byte_units.at(FREE).num_bytes, byte_units.at(FREE).unit)); - break; - - case "free_percentage"_fnv1a16: - case "free_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_ram.free_amount(), query_ram.total_amount(), parse_args, true)); - break; - - case "used_percentage"_fnv1a16: - case "used_perc"_fnv1a16: - SYSINFO_INSERT( - get_and_color_percentage(query_ram.used_amount(), query_ram.total_amount(), parse_args)); - break; - - default: - if (hasStart(moduleMemberName, "free-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.free_amount())); - else if (hasStart(moduleMemberName, "used-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.used_amount())); - else if (hasStart(moduleMemberName, "total-")) - SYSINFO_INSERT(return_devided_bytes(query_ram.total_amount())); - } - } - } - - else if (moduleName == "battery") - { - Query::Battery query_battery; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "percentage"_fnv1a16: - case "perc"_fnv1a16: - SYSINFO_INSERT(get_and_color_percentage(query_battery.perc(), 100, parse_args, true)); - break; - - case "vendor"_fnv1a16: - case "manufacturer"_fnv1a16: SYSINFO_INSERT(query_battery.vendor()); break; - case "technology"_fnv1a16: SYSINFO_INSERT(query_battery.technology()); break; - case "name"_fnv1a16: SYSINFO_INSERT(query_battery.modelname()); break; - case "status"_fnv1a16: SYSINFO_INSERT(query_battery.status()); break; - case "capacity_level"_fnv1a16: SYSINFO_INSERT(query_battery.capacity_level()); break; - - case "temp_C"_fnv1a16: SYSINFO_INSERT(query_battery.temp()); break; - case "temp_F"_fnv1a16: SYSINFO_INSERT(query_battery.temp() * 1.8 + 34); break; - case "temp_K"_fnv1a16: SYSINFO_INSERT(query_battery.temp() + 273.15); break; - } - } - } - - else if (moduleName == "auto") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - switch (moduleMember_hash) - { - case "disk"_fnv1a16: - Query::Disk query_disks("", queried_disks, parse_args, true); - for (const std::string& str : query_disks.disks_formats()) - { - parse_args.tmp_layout.push_back(str); - SYSINFO_INSERT(str); - } - break; - } - } - } - - else - die(_("Invalid module name: {}"), moduleName); - -#undef SYSINFO_INSERT -} - -void addValueFromModule(const std::string& moduleName, parse_args_t& parse_args) -{ - const std::string& moduleMemberName = "module-" + moduleName; -#define SYSINFO_INSERT(x) sysInfo.at(moduleName).insert({ moduleMemberName, variant(x) }) - - // just aliases for convention - const Config& config = parse_args.config; - systemInfo_t& sysInfo = parse_args.systemInfo; - - const std::uint16_t byte_unit = config.use_SI_unit ? 1000 : 1024; - - if (moduleName == "title") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(parse("${auto2}$<user.name>${0}@${auto2}$<os.hostname>", _, parse_args)); - } - } - - else if (moduleName == "title_sep" || moduleName == "title_separator") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // no need to parse anything - Query::User query_user; - Query::System query_system; - const size_t& title_len = - std::string_view(query_user.name() + '@' + query_system.hostname()).length(); - - std::string str; - str.reserve(config.title_sep.length() * title_len); - for (size_t i = 0; i < title_len; i++) - str += config.title_sep; - - SYSINFO_INSERT(str); - } - } - - else if (moduleName == "cpu") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - Query::CPU query_cpu; - SYSINFO_INSERT( - fmt::format("{} ({}) @ {:.2f} GHz", query_cpu.name(), query_cpu.nproc(), query_cpu.freq_max())); - } - } - - else if (hasStart(moduleName, "gpu")) - { - const std::string& id = (moduleName.length() > 3 ? moduleName.substr(3) : "0"); - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - Query::GPU query_gpu(id, queried_gpus); - SYSINFO_INSERT(shorten_vendor_name(query_gpu.vendor()) + " " + query_gpu.name()); - } - } - - else if (hasStart(moduleName, "disk")) - { - if (moduleName.length() < "disk()"_len) - die(_("invalid disk module name '{}', must be disk(/path/to/fs) e.g: disk(/)"), moduleName); - - enum - { - USED = 0, - TOTAL, - }; - std::string path = moduleName; - path.erase(0, 5); // disk( - path.pop_back(); // ) - debug("disk path = {}", path); - - Query::Disk query_disk(path, queried_disks, parse_args); - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - byte_units.at(TOTAL) = auto_devide_bytes(query_disk.total_amount(), byte_unit); - byte_units.at(USED) = auto_devide_bytes(query_disk.used_amount(), byte_unit); - - const std::string& perc = - get_and_color_percentage(query_disk.used_amount(), query_disk.total_amount(), parse_args); - - // clang-format off - std::string result {fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args) - )}; - // clang-format on - if (query_disk.typefs() != MAGIC_LINE) - result += " - " + query_disk.typefs(); - - std::string types_disk {"["}; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_EXTERNAL) - types_disk += "External, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_HIDDEN) - types_disk += "Hidden, "; - if (query_disk.types_disk() & Query::DISK_VOLUME_TYPE_READ_ONLY) - types_disk += "Read-only, "; - - if (types_disk.size() > 3) - { - // ", " - types_disk.erase(types_disk.size() - 2); - result += " " + types_disk + "]"; - } - - SYSINFO_INSERT(result); - } - } - - else if (moduleName == "ram") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - }; - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the divider - byte_units.at(USED) = auto_devide_bytes(query_ram.used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.total_amount() * byte_unit, byte_unit); - - const std::string& perc = - get_and_color_percentage(query_ram.used_amount(), query_ram.total_amount(), parse_args); - - // clang-format off - SYSINFO_INSERT(fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args))); - // clang-format on - } - } - - else if (moduleName == "swap") - { - Query::RAM query_ram; - enum - { - USED = 0, - TOTAL, - }; - std::array<byte_units_t, 2> byte_units; - - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - // idk, trick the divider - byte_units.at(USED) = auto_devide_bytes(query_ram.swap_used_amount() * byte_unit, byte_unit); - byte_units.at(TOTAL) = auto_devide_bytes(query_ram.swap_total_amount() * byte_unit, byte_unit); - - // clang-format off - if (byte_units.at(TOTAL).num_bytes < 1) - SYSINFO_INSERT("Disabled"); - else - { - const std::string& perc = get_and_color_percentage(query_ram.swap_used_amount(), query_ram.swap_total_amount(), - parse_args); - - SYSINFO_INSERT(fmt::format("{:.2f} {} / {:.2f} {} {}", - byte_units.at(USED).num_bytes, byte_units.at(USED).unit, - byte_units.at(TOTAL).num_bytes,byte_units.at(TOTAL).unit, - parse("${0}(" + perc + ")", _, parse_args))); - } - // clang-format on - } - } - - else if (moduleName == "battery") - { - Query::Battery query_battery; - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - SYSINFO_INSERT(fmt::format("{} [{}]", get_and_color_percentage(query_battery.perc(), 100, parse_args, true), - query_battery.status())); - } - } - - else if (moduleName == "colors") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { -#if !ANDROID_APP - SYSINFO_INSERT(parse("${\033[40m} ${\033[41m} ${\033[42m} ${\033[43m} ${\033[44m} ${\033[45m} ${\033[46m} ${\033[47m} ${0}", _, parse_args)); -#else // bruh why the android HTML implementation gotta be so dumb - SYSINFO_INSERT(parse("${\033[40m}   ${\033[41m}   ${\033[42m}   ${\033[43m}   ${\033[44m}   ${\033[45m}   ${\033[46m}   ${\033[47m}   ${0}", _, parse_args)); -#endif - } - } - - else if (moduleName == "colors_light") - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { -#if !ANDROID_APP - SYSINFO_INSERT(parse("${\033[100m} ${\033[101m} ${\033[102m} ${\033[103m} ${\033[104m} ${\033[105m} ${\033[106m} ${\033[107m} ${0}", _, parse_args)); -#else - SYSINFO_INSERT(parse("${\033[100m}   ${\033[101m}   ${\033[102m}   ${\033[103m}   ${\033[104m}   ${\033[105m}   ${\033[106m}   ${\033[107m}   ${0}", _, parse_args)); -#endif - } - } - - // clang-format off - // I really dislike how repetitive this code is - else if (hasStart(moduleName, "colors_symbol")) - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (moduleName.length() <= "colors_symbol()"_len) - die(_("color palette module member '{}' in invalid.\n" - "Must be used like 'colors_symbol(`symbol for printing the color palette`)'.\n" - "e.g 'colors_symbol(@)' or 'colors_symbol(string)'"), - moduleName); - - std::string symbol = moduleName; - symbol.erase(0, "colors_symbol("_len); - symbol.pop_back(); - debug("symbol = {}", symbol); - - SYSINFO_INSERT( - parse(fmt::format("${{\033[30m}} {0} ${{\033[31m}} {0} ${{\033[32m}} {0} ${{\033[33m}} {0} ${{\033[34m}} {0} ${{\033[35m}} {0} ${{\033[36m}} {0} ${{\033[37m}} {0} ${{0}}", - symbol), _, parse_args)); - } - } - - else if (hasStart(moduleName, "colors_light_symbol")) - { - if (sysInfo.find(moduleName) == sysInfo.end()) - sysInfo.insert({ moduleName, {} }); - - if (sysInfo.at(moduleName).find(moduleMemberName) == sysInfo.at(moduleName).end()) - { - if (moduleName.length() <= "colors_light_symbol()"_len) - die(_("light color palette module member '{}' in invalid.\n" - "Must be used like 'colors_light_symbol(`symbol for printing the color palette`)'.\n" - "e.g 'colors_light_symbol(@)' or 'colors_light_symbol(string)'"), - moduleName); - - std::string symbol = moduleName; - symbol.erase(0, "colors_light_symbol("_len); - symbol.pop_back(); - debug("symbol = {}", symbol); - - SYSINFO_INSERT( - parse(fmt::format("${{\033[90m}} {0} ${{\033[91m}} {0} ${{\033[92m}} {0} ${{\033[93m}} {0} ${{\033[94m}} {0} ${{\033[95m}} {0} ${{\033[96m}} {0} ${{\033[97m}} {0} ${{0}}", - symbol), _, parse_args)); - } - } - - else - die(_("Invalid module name: {}"), moduleName); -} diff --git a/src/query/android/battery.cpp b/src/query/android/battery.cpp deleted file mode 100644 index d84efeff..00000000 --- a/src/query/android/battery.cpp +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <cctype> -#include <string> -#include <vector> - -#include "json.h" -#include "query.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -using namespace Query; - -static Battery::Battery_t get_battery_infos_termux() -{ - Battery::Battery_t infos; - std::string result, _; - if (!read_exec({ "/data/data/com.termux/files/usr/libexec/termux-api", "BatteryStatus" }, result)) - return infos; - - const auto& doc = json::jobject::parse(result); - - infos.status = doc["plugged"].as_string(); - infos.perc = doc["percentage"]; - infos.temp = doc["temperature"]; - - switch (fnv1a16::hash(infos.status)) - { - case "PLUGGED_AC"_fnv1a16: infos.status = "AC Connected, "; break; - case "PLUGGED_USB"_fnv1a16: infos.status = "USB Connected, "; break; - case "PLUGGED_WIRELESS"_fnv1a16: infos.status = "Wireless Connected, "; break; - default: infos.status.clear(); - } - - // CHARGING or DISCHARGING - std::string charge_status{ str_tolower(doc["status"].as_string()) }; - charge_status.at(0) = toupper(charge_status.at(0)); - infos.status += charge_status; - - return infos; -} - -static Battery::Battery_t get_battery_infos_dumpsys() -{ - Battery::Battery_t infos; - std::string result, _; - if (!read_exec({ "/system/bin/dumpsys", "battery" }, result)) - return infos; - - const std::vector<std::string>& vec = split(result, '\n'); - if (vec.at(0) != "Current Battery Service state:") - return infos; - - double level = 0, scale = 0; - for (size_t i = 1; i < vec.size(); ++i) - { - const size_t pos = vec.at(i).rfind(':'); - const std::string& key = vec.at(i).substr(2, pos); - const std::string& value = vec.at(i).substr(pos + 2); - - switch (fnv1a16::hash(key)) - { - case "level"_fnv1a16: level = std::stod(value); break; - case "scale"_fnv1a16: scale = std::stod(value); break; - - case "AC powered"_fnv1a16: - case "USB powered"_fnv1a16: - case "Dock powered"_fnv1a16: - case "Wireless powered"_fnv1a16: - if (value == "true") - infos.status = key; - break; - - case "temperature"_fnv1a16: infos.temp = std::stod(value) / 10; break; - case "technology"_fnv1a16: infos.technology = value; - } - } - - if (level > 0 && scale > 0) - infos.perc = level * 100 / scale; - - return infos; -} - -Battery::Battery() -{ - CHECK_INIT(!m_bInit) - { -// can't execute commands in android app -// also this is a widget, you can see the percentage in your topbar -#if !ANDROID_APP - m_battery_infos = get_battery_infos_termux(); - if (m_battery_infos.status == MAGIC_LINE || m_battery_infos.temp <= 0) - m_battery_infos = get_battery_infos_dumpsys(); -#endif - } - - m_bInit = true; -} - -// clang-format off -std::string& Battery::modelname() noexcept -{ return m_battery_infos.modelname; } - -std::string& Battery::status() noexcept -{ return m_battery_infos.status; } - -std::string& Battery::vendor() noexcept -{ return m_battery_infos.vendor; } - -std::string& Battery::technology() noexcept -{ return m_battery_infos.technology; } - -std::string& Battery::capacity_level() noexcept -{ return m_battery_infos.capacity_level; } - -double& Battery::perc() noexcept -{ return m_battery_infos.perc; } - -double& Battery::temp() noexcept -{ return m_battery_infos.temp; } - -#endif // CF_ANDROID diff --git a/src/query/android/system.cpp b/src/query/android/system.cpp deleted file mode 100644 index b3bef492..00000000 --- a/src/query/android/system.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <sys/stat.h> -#include <unistd.h> - -#include <array> -#include <string_view> - -#include "../linux/utils/packages.hpp" -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static System::System_t get_system_infos() -{ - System::System_t ret; - - ret.os_name = "Android"; - ret.os_id = "android"; - ret.os_version_id = get_android_property("ro.build.version.release"); - ret.os_version_codename = get_android_property("ro.build.version.codename"); - ret.os_pretty_name = "Android " + ret.os_version_codename + " " + ret.os_version_id; - - constexpr std::array<std::string_view, 8> vendors_prop_names = { - "ro.product.marketname", "ro.vendor.product.display", "ro.config.devicename", "ro.config.marketing_name", - "ro.product.vendor.model", "ro.product.oppo_model", "ro.oppo.market.name", "ro.product.brand" - }; - for (const std::string_view name : vendors_prop_names) - { - if (ret.host_modelname.empty() || ret.host_modelname == UNKNOWN) - ret.host_modelname = get_android_property(name); - else - break; - } - ret.host_vendor = get_android_property("ro.product.manufacturer"); - ret.host_version = get_android_property("ro.product.model"); - - if (access("/system/bin/init", F_OK) == 0) - { - ret.os_initsys_name = "init"; - ret.os_initsys_version.clear(); - } - - return ret; -} - -System::System() -{ - CHECK_INIT(!m_bInit) - { - if (uname(&m_uname_infos) != 0) - die("uname() failed: {}\nCould not get system infos", strerror(errno)); - - if (sysinfo(&m_sysInfos) != 0) - die("sysinfo() failed: {}\nCould not get system infos", strerror(errno)); - - m_system_infos = get_system_infos(); - } - m_bInit = true; -} - -// clang-format off -std::string System::kernel_name() noexcept -{ return m_uname_infos.sysname; } - -std::string System::kernel_version() noexcept -{ return m_uname_infos.release; } - -std::string System::hostname() noexcept -{ return m_uname_infos.nodename; } - -std::string System::arch() noexcept -{ return m_uname_infos.machine; } - -long& System::uptime() noexcept -{ return m_sysInfos.uptime; } - -std::string& System::os_pretty_name() noexcept -{ return m_system_infos.os_pretty_name; } - -std::string& System::os_name() noexcept -{ return m_system_infos.os_name; } - -std::string& System::os_id() noexcept -{ return m_system_infos.os_id; } - -std::string& System::os_versionid() noexcept -{ return m_system_infos.os_version_id; } - -std::string& System::os_version_codename() noexcept -{ return m_system_infos.os_version_codename; } - -std::string& System::host_modelname() noexcept -{ return m_system_infos.host_modelname; } - -std::string& System::host_vendor() noexcept -{ return m_system_infos.host_vendor; } - -std::string& System::host_version() noexcept -{ return m_system_infos.host_version; } - -std::string& System::os_initsys_name() -{ return m_system_infos.os_initsys_name; } - -std::string& System::os_initsys_version() -{ return m_system_infos.os_initsys_version; } - -std::string& System::pkgs_installed(const Config& config) -{ - static bool done = false; - if (!done) - { - m_system_infos.pkgs_installed = get_all_pkgs(config); - - done = true; - } - - return m_system_infos.pkgs_installed; -} - -#endif diff --git a/src/query/android/user.cpp b/src/query/android/user.cpp deleted file mode 100644 index 36a4a930..00000000 --- a/src/query/android/user.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the - * following disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote - * products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_ANDROID - -#include <linux/limits.h> -#include <unistd.h> - -#include <cstdlib> -#include <string> - -#include "fmt/format.h" -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static std::string get_shell_version(const std::string_view shell_name) -{ - std::string ret; - - if (shell_name == "nu") - ret = read_shell_exec("nu -c \"version | get version\""); - else - ret = read_shell_exec(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data()))); - - strip(ret); - return ret; -} - -static std::string get_shell_name(const std::string_view shell_path) -{ - return shell_path.substr(shell_path.rfind('/') + 1).data(); -} - -User::User() noexcept -{ - CHECK_INIT(!m_bInit) - { - const uid_t uid = getuid(); - - if (m_pPwd = getpwuid(uid), !m_pPwd) - die("getpwent failed: {}\nCould not get user infos", std::strerror(errno)); - - char buf[PATH_MAX]; - if (getenv("TERMUX_VERSION") || getenv("TERMUX_MAIN_PACKAGE_FORMAT")) - { - m_users_infos.shell_path = realpath(fmt::format("/proc/{}/exe", getppid()).c_str(), buf); - m_users_infos.shell_name = get_shell_name(m_users_infos.shell_path); - m_users_infos.shell_version = get_shell_version(m_users_infos.shell_name); - m_users_infos.term_name = "Termux"; - m_users_infos.term_version = getenv("TERMUX_VERSION"); - } - else - { - m_users_infos.shell_path = m_pPwd->pw_shell; - } - - m_users_infos.wm_name = m_users_infos.wm_version = m_users_infos.de_name = m_users_infos.de_version = - m_users_infos.m_wm_path = MAGIC_LINE; - } - m_bInit = true; -} - -// clang-format off -std::string User::name() noexcept -{ return m_pPwd->pw_name; } - -std::string User::shell_path() noexcept -{ return m_users_infos.shell_path; } - -std::string& User::shell_name() noexcept -{ return m_users_infos.shell_name; } - -std::string& User::shell_version(const std::string_view shell_name) -{ return m_users_infos.shell_version; } - -std::string& User::term_name() -{ return m_users_infos.term_name; } - -std::string& User::term_version(const std::string_view term_name) -{ return m_users_infos.term_version; } - -std::string& User::wm_name(bool dont_query_dewm, const std::string_view term_name) -{ return m_users_infos.wm_name; } - -std::string& User::wm_version(bool dont_query_dewm, const std::string_view term_name) -{ return m_users_infos.wm_version; } - -std::string& User::de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name) -{ return m_users_infos.de_name; } - -std::string& User::de_version(const std::string_view de_name) -{ return m_users_infos.de_version; } - -#endif diff --git a/src/query/linux/battery.cpp b/src/query/linux/battery.cpp deleted file mode 100644 index ea1d7388..00000000 --- a/src/query/linux/battery.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <cstdlib> -#include <string> -#include <filesystem> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -static void read_strip_syspath(std::string& str, const std::string_view path) -{ - str = read_by_syspath(path); - debug("str = {} || path = {}", str, path); - - // optimization - if (str.back() == '\n') - str.pop_back(); - else if (str != UNKNOWN) - strip(str); -} - -static Battery::Battery_t get_battery_infos() -{ - Battery::Battery_t infos; - if (!std::filesystem::exists("/sys/class/power_supply/")) - return infos; - - for (const auto& dir_entry : std::filesystem::directory_iterator{"/sys/class/power_supply/"}) - { - const std::string& path = dir_entry.path().string() + "/"; - debug("battery path = {}", path); - std::string tmp; - - read_strip_syspath(tmp, path + "type"); - if (tmp == UNKNOWN || tmp != "Battery") - continue; - - read_strip_syspath(tmp, path + "scope"); - if (tmp == "Device") - continue; - - debug("battery found yeappyy"); - read_strip_syspath(tmp, path + "capacity"); - if (tmp != UNKNOWN) - infos.perc = std::stod(tmp); - else - continue; - - read_strip_syspath(tmp, path + "temp"); - if (tmp != UNKNOWN) - infos.temp = std::stod(tmp) / 10; - - read_strip_syspath(tmp, path + "manufacturer"); - if (tmp != UNKNOWN) - infos.vendor = tmp; - - read_strip_syspath(tmp, path + "model_name"); - if (tmp != UNKNOWN) - infos.modelname = tmp; - - read_strip_syspath(tmp, path + "technology"); - if (tmp != UNKNOWN) - infos.technology = tmp; - - read_strip_syspath(tmp, path + "status"); - if (tmp != UNKNOWN) - infos.status = tmp; - - read_strip_syspath(tmp, path + "capacity_level"); - if (tmp != UNKNOWN) - infos.capacity_level = tmp; - } - - return infos; -} - -Battery::Battery() -{ - CHECK_INIT(!m_bInit) - { - m_battery_infos = get_battery_infos(); - } - - m_bInit = true; -} - -// clang-format off -std::string& Battery::modelname() noexcept -{ return m_battery_infos.modelname; } - -std::string& Battery::status() noexcept -{ return m_battery_infos.status; } - -std::string& Battery::vendor() noexcept -{ return m_battery_infos.vendor; } - -std::string& Battery::technology() noexcept -{ return m_battery_infos.technology; } - -std::string& Battery::capacity_level() noexcept -{ return m_battery_infos.capacity_level; } - -double& Battery::perc() noexcept -{ return m_battery_infos.perc; } - -double& Battery::temp() noexcept -{ return m_battery_infos.temp; } - -#endif // CF_LINUX diff --git a/src/query/linux/disk.cpp b/src/query/linux/disk.cpp deleted file mode 100644 index 95975894..00000000 --- a/src/query/linux/disk.cpp +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -/* - * Copyright (c) 2021-2023 Linus Dierheimer - * Copyright (c) 2022-2024 Carter Li - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#include "platform.hpp" -#if CF_LINUX || CF_ANDROID - -#include <mntent.h> -#include <cstdio> -#include <cstring> -#include <unistd.h> -#include <algorithm> -#include <string_view> - -#include "config.hpp" -#include "query.hpp" -#include "util.hpp" -#include "parse.hpp" - -using namespace Query; - -// https://github.com/fastfetch-cli/fastfetch/blob/dev/src/detection/disk/disk_linux.c -static bool is_physical_device(const mntent* device) -{ - #if !CF_ANDROID // On Android, `/dev` is not accessible, so that the following checks always fail - - //Always show the root path - if (strcmp(device->mnt_dir, "/") == 0) - return true; - - if (strcmp(device->mnt_fsname, "none") == 0) - return false; - - //DrvFs is a filesystem plugin to WSL that was designed to support interop between WSL and the Windows filesystem. - if (strcmp(device->mnt_type, "9p") == 0) - return std::string_view(device->mnt_opts).find("aname=drvfs") != std::string_view::npos; - - //ZFS pool - if (strcmp(device->mnt_type, "zfs") == 0) - return true; - - //Pseudo filesystems don't have a device in /dev - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - //#731 - if (strcmp(device->mnt_type, "bcachefs") == 0) - return true; - - if( - hasStart(device->mnt_fsname + 5, "loop") || //Ignore loop devices - hasStart(device->mnt_fsname + 5, "ram") || //Ignore ram devices - hasStart(device->mnt_fsname + 5, "fd") //Ignore fd devices - ) return false; - - struct stat deviceStat; - if (stat(device->mnt_fsname, &deviceStat) != 0) - return false; - - //Ignore all devices that are not block devices - if (!S_ISBLK(deviceStat.st_mode)) - return false; - - #else - - //Pseudo filesystems don't have a device in /dev - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - if( - hasStart(device->mnt_fsname + 5, "loop") || //Ignore loop devices - hasStart(device->mnt_fsname + 5, "ram") || //Ignore ram devices - hasStart(device->mnt_fsname + 5, "fd") //Ignore fd devices - ) return false; - - // https://source.android.com/docs/core/ota/apex?hl=zh-cn - if (hasStart(device->mnt_dir, "/apex/")) - return false; - - #endif // !CF_ANDROID - - return true; -} - -static bool is_removable(const mntent* device) -{ - if (!hasStart(device->mnt_fsname, "/dev/")) - return false; - - // like str.substr(5); - std::string sys_block_partition {fmt::format("/sys/class/block/{}", (device->mnt_fsname + "/dev/"_len))}; - // check if it's like /dev/sda1 - if (sys_block_partition.back() >= '0' && sys_block_partition.back() <= '9') - sys_block_partition.pop_back(); - - return read_by_syspath(sys_block_partition + "/removable") == "1"; -} - -static int get_disk_type(const mntent* device) -{ -#if CF_LINUX - int ret = 0; - - if (hasStart(device->mnt_dir, "/boot") || hasStart(device->mnt_dir, "/efi")) - ret = DISK_VOLUME_TYPE_HIDDEN; - else if (is_removable(device)) - ret = DISK_VOLUME_TYPE_EXTERNAL; - else - ret = DISK_VOLUME_TYPE_REGULAR; - - if (hasmntopt(device, MNTOPT_RO)) - ret |= DISK_VOLUME_TYPE_READ_ONLY; - - return ret; -#else // CF_ANDROID - if (strcmp(device->mnt_dir, "/") == 0 || strcmp(device->mnt_dir, "/storage/emulated") == 0) - return DISK_VOLUME_TYPE_REGULAR; - - if (hasStart(device->mnt_dir, "/mnt/media_rw/")) - return DISK_VOLUME_TYPE_EXTERNAL; - - return DISK_VOLUME_TYPE_HIDDEN; -#endif -} - -static std::string format_auto_query_string(std::string str, const struct mntent *device) -{ - replace_str(str, "%1", device->mnt_dir); - replace_str(str, "%2", device->mnt_fsname); - replace_str(str, "%3", device->mnt_type); - - replace_str(str, "%4", fmt::format("$<disk({}).total>", device->mnt_dir)); - replace_str(str, "%5", fmt::format("$<disk({}).free>", device->mnt_dir)); - replace_str(str, "%6", fmt::format("$<disk({}).used>", device->mnt_dir)); - replace_str(str, "%7", fmt::format("$<disk({}).used_perc>", device->mnt_dir)); - replace_str(str, "%8", fmt::format("$<disk({}).free_perc>", device->mnt_dir)); - - return str; -} - -Disk::Disk(const std::string& path, systemInfo_t& queried_paths, parse_args_t& parse_args, const bool auto_module) -{ - if (queried_paths.find(path) != queried_paths.end() && !is_live_mode) - { - m_disk_infos.device = getInfoFromName(queried_paths, path, "device"); - m_disk_infos.mountdir = getInfoFromName(queried_paths, path, "mountdir"); - m_disk_infos.typefs = getInfoFromName(queried_paths, path, "typefs"); - m_disk_infos.total_amount = std::stod(getInfoFromName(queried_paths, path, "total_amount")); - m_disk_infos.used_amount = std::stod(getInfoFromName(queried_paths, path, "used_amount")); - m_disk_infos.free_amount = std::stod(getInfoFromName(queried_paths, path, "free_amount")); - return; - } - - if (access(path.data(), F_OK) != 0 && !auto_module) - { - // if user is using $<disk(path)> or $<disk(path).fs> - // then let's just "try" to remove it - m_disk_infos.typefs = MAGIC_LINE; - m_disk_infos.device = MAGIC_LINE; - m_disk_infos.mountdir = MAGIC_LINE; - return; - } - - FILE* mountsFile = setmntent("/proc/mounts", "r"); - if (mountsFile == NULL) - { - perror("setmntent"); - error(_("setmntent() failed. Could not get disk info")); - return; - } - - if (auto_module) - { - struct mntent* pDevice; - while ((pDevice = getmntent(mountsFile))) - { - if (!is_physical_device(pDevice)) - continue; - - m_disk_infos.types_disk = get_disk_type(pDevice); - if (!(parse_args.config.auto_disks_types & m_disk_infos.types_disk)) - continue; - - if (!parse_args.config.auto_disks_show_dupl) - { - const auto& it = std::find(m_queried_devices.begin(), m_queried_devices.end(), pDevice->mnt_fsname); - if (it != m_queried_devices.end()) - continue; - - m_queried_devices.push_back(pDevice->mnt_fsname); - } - - parse_args.no_more_reset = false; - debug("AUTO: pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); - m_disks_formats.push_back( - parse(format_auto_query_string(parse_args.config.auto_disks_fmt, pDevice), parse_args) - ); - } - - endmntent(mountsFile); - return; - } - - struct mntent* pDevice; - while ((pDevice = getmntent(mountsFile))) - { - debug("pDevice->mnt_dir = {} && pDevice->mnt_fsname = {}", pDevice->mnt_dir, pDevice->mnt_fsname); - if (path == pDevice->mnt_dir || path == pDevice->mnt_fsname) - { - m_disk_infos.types_disk = get_disk_type(pDevice); - if (!(parse_args.config.auto_disks_types & m_disk_infos.types_disk)) - continue; - - m_disk_infos.typefs = pDevice->mnt_type; - m_disk_infos.device = pDevice->mnt_fsname; - m_disk_infos.mountdir = pDevice->mnt_dir; - break; - } - } - - const std::string& statpath = (hasStart(path, "/dev") && pDevice) ? pDevice->mnt_dir : path; - - if (statvfs(statpath.c_str(), &m_statvfs) != 0) - { - perror("statvfs"); - error(_("Failed to get disk info at {}"), statpath); - return; - } - - m_disk_infos.total_amount = static_cast<double>(m_statvfs.f_blocks * m_statvfs.f_frsize); - m_disk_infos.free_amount = static_cast<double>(m_statvfs.f_bfree * m_statvfs.f_frsize); - m_disk_infos.used_amount = m_disk_infos.total_amount - m_disk_infos.free_amount; - - endmntent(mountsFile); - queried_paths.insert( - {path, { - {"total_amount", variant(m_disk_infos.total_amount)}, - {"used_amount", variant(m_disk_infos.used_amount)}, - {"free_amount", variant(m_disk_infos.free_amount)}, - {"typefs", variant(m_disk_infos.typefs)}, - {"mountdir", variant(m_disk_infos.mountdir)}, - {"device", variant(m_disk_infos.device)} - }} - ); -} - -// clang-format off -double& Disk::total_amount() noexcept -{ return m_disk_infos.total_amount; } - -double& Disk::used_amount() noexcept -{ return m_disk_infos.used_amount; } - -double& Disk::free_amount() noexcept -{ return m_disk_infos.free_amount; } - -int& Disk::types_disk() noexcept -{ return m_disk_infos.types_disk; } - -std::string& Disk::typefs() noexcept -{ return m_disk_infos.typefs; } - -std::string& Disk::mountdir() noexcept -{ return m_disk_infos.mountdir; } - -std::string& Disk::device() noexcept -{ return m_disk_infos.device; } - -#endif // CF_LINUX || CF_ANDROID diff --git a/src/query/linux/gpu.cpp b/src/query/linux/gpu.cpp deleted file mode 100644 index 3d323dd5..00000000 --- a/src/query/linux/gpu.cpp +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <cstdint> -#include <filesystem> -#include <string> -#include "fmt/format.h" - -#include "query.hpp" -#include "util.hpp" -#include "parse.hpp" - -using namespace Query; - -static std::string get_name(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) -{ - std::string name = binarySearchPCIArray(m_vendor_id_s, m_device_id_s); - debug("GPU binarySearchPCIArray name = {}", name); - const size_t first_bracket = name.find('['); - const size_t last_bracket = name.rfind(']'); - - // remove the chips name "TU106 [GeForce GTX 1650]" - // This should work for AMD and Intel too. - if (first_bracket != std::string::npos && last_bracket != std::string::npos) - name = name.substr(first_bracket + 1, last_bracket - first_bracket - 1); - - // name = this->vendor() + ' ' + name; - - // replace_str(name, "NVIDIA Corporation", "NVIDIA"); - // replace_str(name, "Advanced Micro Devices Inc.", "AMD"); - // replace_str(name, "Intel Corporation", "Intel"); - - return name; -} - -static std::string get_vendor(const std::string_view m_vendor_id_s) -{ return binarySearchPCIArray(m_vendor_id_s); } - -static GPU::GPU_t get_gpu_infos(const std::string_view m_vendor_id_s, const std::string_view m_device_id_s) -{ - debug("calling GPU {}", __func__); - GPU::GPU_t ret; - - debug("GPU m_vendor_id_s = {} || m_device_id_s = {}", m_vendor_id_s, m_device_id_s); - if (m_device_id_s == UNKNOWN || m_vendor_id_s == UNKNOWN) - return ret; - - ret.name = get_name(m_vendor_id_s, m_device_id_s); - ret.vendor = get_vendor(m_vendor_id_s); - - return ret; -} - -GPU::GPU(const std::string& id, systemInfo_t& queried_gpus) -{ - if (queried_gpus.find(id) != queried_gpus.end()) - { - m_gpu_infos.name = getInfoFromName(queried_gpus, id, "name"); - m_gpu_infos.vendor = getInfoFromName(queried_gpus, id, "vendor"); - return; - } - - const std::uint16_t max_iter = 10; - std::uint16_t id_iter = std::stoi(id); - std::string sys_path; - int i = 0; - for (; i <= max_iter; i++) - { - sys_path = "/sys/class/drm/card" + fmt::to_string(id_iter); - if (std::filesystem::exists(sys_path + "/device/device") && - std::filesystem::exists(sys_path + "/device/vendor")) - break; - else - id_iter++; - } - - if (i >= max_iter) - { - error(_("Failed to parse GPU infos on the path /sys/class/drm/")); - return; - } - - m_vendor_id_s = read_by_syspath(sys_path + "/device/vendor"); - m_device_id_s = read_by_syspath(sys_path + "/device/device"); - - m_gpu_infos = get_gpu_infos(m_vendor_id_s, m_device_id_s); - queried_gpus.insert( - {id, { - {"name", variant(m_gpu_infos.name)}, - {"vendor", variant(m_gpu_infos.vendor)}, - }} - ); -} - -// clang-format off -std::string& GPU::name() noexcept -{ return m_gpu_infos.name; } - -std::string& GPU::vendor() noexcept -{ return m_gpu_infos.vendor; } - -#endif diff --git a/src/query/linux/ram.cpp b/src/query/linux/ram.cpp deleted file mode 100644 index bc204991..00000000 --- a/src/query/linux/ram.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX || CF_ANDROID - -#include <fstream> - -#include "query.hpp" -#include "util.hpp" - -using namespace Query; - -/*enum { - SHMEM = 0, - FREE, - BUFFER, - CACHED, - SRECLAIMABLE -};*/ - -static size_t get_from_text(std::string& line, u_short& iter_index, const std::uint16_t len) -{ - std::string amount = line.substr(len + 1); - strip(amount); - ++iter_index; - return std::stoi(amount); -} - -static RAM::RAM_t get_amount() noexcept -{ - debug("calling in RAM {}", __PRETTY_FUNCTION__); - constexpr std::string_view meminfo_path = "/proc/meminfo"; - RAM::RAM_t memory_infos; - - // std::array<size_t, 5> extra_mem_info; - std::ifstream file(meminfo_path.data()); - if (!file.is_open()) - { - error(_("Could not open {}\nFailed to get RAM infos"), meminfo_path); - return memory_infos; - } - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(file, line) && iter_index < 4) - { - if (hasStart(line, "MemAvailable:")) - memory_infos.free_amount = get_from_text(line, iter_index, "MemAvailable:"_len); - - else if (hasStart(line, "MemTotal:")) - memory_infos.total_amount = get_from_text(line, iter_index, "MemTotal:"_len); - - else if (hasStart(line, "SwapFree:")) - memory_infos.swap_free_amount = get_from_text(line, iter_index, "SwapFree:"_len); - - else if (hasStart(line, "SwapTotal:")) - memory_infos.swap_total_amount = get_from_text(line, iter_index, "SwapTotal:"_len); - - /*if (line.find("Shmem:") != std::string::npos) - extra_mem_info.at(SHMEM) = get_from_text(line); - - if (line.find("MemFree:") != std::string::npos) - extra_mem_info.at(FREE) = get_from_text(line); - - if (line.find("Buffers:") != std::string::npos) - extra_mem_info.at(BUFFER) = get_from_text(line); - - if (line.find("Cached:") != std::string::npos) - extra_mem_info.at(CACHED) = get_from_text(line); - - if (line.find("SReclaimable:") != std::string::npos) - extra_mem_info.at(SRECLAIMABLE) = get_from_text(line);*/ - } - - // https://github.com/dylanaraps/neofetch/wiki/Frequently-Asked-Questions#linux-is-neofetchs-memory-output-correct - memory_infos.used_amount = - memory_infos.total_amount - - memory_infos.free_amount; // + extra_mem_info.at(SHMEM) - extra_mem_info.at(FREE) - extra_mem_info.at(BUFFER) - - // extra_mem_info.at(CACHED) - extra_mem_info.at(SRECLAIMABLE); - - memory_infos.swap_used_amount = - memory_infos.swap_total_amount - - memory_infos.swap_free_amount; - - return memory_infos; -} - -RAM::RAM() noexcept -{ - CHECK_INIT(!m_bInit) - { - m_memory_infos = get_amount(); - m_bInit = true; - } -} - -// clang-format off -double& RAM::free_amount() noexcept -{ return m_memory_infos.free_amount; } - -double& RAM::total_amount() noexcept -{ return m_memory_infos.total_amount; } - -double& RAM::used_amount() noexcept -{ return m_memory_infos.used_amount; } - -double& RAM::swap_total_amount() noexcept -{ return m_memory_infos.swap_total_amount; } - -double& RAM::swap_used_amount() noexcept -{ return m_memory_infos.swap_used_amount; } - -double& RAM::swap_free_amount() noexcept -{ return m_memory_infos.swap_free_amount; } - -#endif // CF_LINUX || CF_ANDROID diff --git a/src/query/linux/system.cpp b/src/query/linux/system.cpp deleted file mode 100644 index 8dd149e7..00000000 --- a/src/query/linux/system.cpp +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <linux/limits.h> -#include <unistd.h> - -#include <array> -#include <cerrno> -#include <cstdint> -#include <cstdlib> -#include <cstring> -#include <filesystem> - -#include "config.hpp" -#include "query.hpp" -#include "util.hpp" -#include "switch_fnv1a.hpp" -#include "utils/packages.hpp" - -using namespace Query; - -static void get_host_paths(System::System_t& paths) -{ - const std::string syspath = "/sys/devices/virtual/dmi/id"; - - if (std::filesystem::exists(syspath + "/board_name")) - { - paths.host_modelname = read_by_syspath(syspath + "/board_name"); - paths.host_version = read_by_syspath(syspath + "/board_version"); - paths.host_vendor = read_by_syspath(syspath + "/board_vendor"); - - if (paths.host_vendor == "Micro-Star International Co., Ltd.") - paths.host_vendor = "MSI"; - } - - else if (std::filesystem::exists(syspath + "/product_name")) - { - paths.host_modelname = read_by_syspath(syspath + "/product_name"); - if (hasStart(paths.host_modelname, "Standard PC")) - { - // everyone does it like "KVM/QEMU Standard PC (...) (host_version)" so why not - paths.host_vendor = "KVM/QEMU"; - paths.host_version = std::string_view('(' + read_by_syspath(syspath + "/product_version") + ')').data(); - } - else - paths.host_version = read_by_syspath(syspath + "/product_version"); - } -} - -static System::System_t get_system_infos_lsb_releases() -{ - System::System_t ret; - - debug("calling in System {}", __PRETTY_FUNCTION__); - std::string lsb_release_path; - constexpr std::array<std::string_view, 3> lsb_paths = { "/etc/lsb-release", "/usr/lib/lsb-release" }; - for (const std::string_view path : lsb_paths) - { - if (std::filesystem::exists(path)) - { - lsb_release_path = path; - break; - } - } - - std::ifstream os_release_file(lsb_release_path, std::ios::in); - if (!os_release_file.is_open()) - { - error(_("Failed to get OS infos"), lsb_release_path); - return ret; - } - - // get OS /etc/lsb-release infos - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(os_release_file, line) && iter_index < 3) - { - if (hasStart(line, "DISTRIB_DESCRIPTION=")) - getFileValue(iter_index, line, ret.os_pretty_name, "DISTRIB_DESCRIPTION="_len); - - else if (hasStart(line, "DISTRIB_ID=")) - getFileValue(iter_index, line, ret.os_id, "DISTRIB_ID="_len); - - else if (hasStart(line, "DISTRIB_CODENAME=")) - getFileValue(iter_index, line, ret.os_version_codename, "DISTRIB_CODENAME="_len); - } - - return ret; -} - -static System::System_t get_system_infos_os_releases() -{ - System::System_t ret; - - debug("calling in System {}", __PRETTY_FUNCTION__); - std::string os_release_path; - constexpr std::array<std::string_view, 3> os_paths = { "/etc/os-release", "/usr/lib/os-release", "/usr/share/os-release" }; - for (const std::string_view path : os_paths) - { - if (std::filesystem::exists(path)) - { - os_release_path = path; - break; - } - } - - std::ifstream os_release_file(os_release_path, std::ios::in); - if (!os_release_file.is_open()) - { - //error(_("Could not open '{}'\nFailed to get OS infos"), os_release_path); - return ret; - } - - // get OS /etc/os-release infos - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(os_release_file, line) && iter_index < 5) - { - if (hasStart(line, "PRETTY_NAME=")) - getFileValue(iter_index, line, ret.os_pretty_name, "PRETTY_NAME="_len); - - else if (hasStart(line, "NAME=")) - getFileValue(iter_index, line, ret.os_name, "NAME="_len); - - else if (hasStart(line, "ID=")) - getFileValue(iter_index, line, ret.os_id, "ID="_len); - - else if (hasStart(line, "VERSION_ID=")) - getFileValue(iter_index, line, ret.os_version_id, "VERSION_ID="_len); - - else if (hasStart(line, "VERSION_CODENAME=")) - getFileValue(iter_index, line, ret.os_version_codename, "VERSION_CODENAME="_len); - } - - return ret; -} - -System::System() -{ - CHECK_INIT(!m_bInit) - { - if (uname(&m_uname_infos) != 0) - die(_("uname() failed: {}\nCould not get system infos"), strerror(errno)); - - if (sysinfo(&m_sysInfos) != 0) - die(_("sysinfo() failed: {}\nCould not get system infos"), strerror(errno)); - - m_system_infos = get_system_infos_os_releases(); - if (m_system_infos.os_name == UNKNOWN || m_system_infos.os_pretty_name == UNKNOWN) - m_system_infos = get_system_infos_lsb_releases(); - - get_host_paths(m_system_infos); - m_bInit = true; - } -} - -// clang-format off -std::string System::kernel_name() noexcept -{ return m_uname_infos.sysname; } - -std::string System::kernel_version() noexcept -{ return m_uname_infos.release; } - -std::string System::hostname() noexcept -{ return m_uname_infos.nodename; } - -std::string System::arch() noexcept -{ return m_uname_infos.machine; } - -long& System::uptime() noexcept -{ return m_sysInfos.uptime; } - -std::string& System::os_pretty_name() noexcept -{ return m_system_infos.os_pretty_name; } - -std::string& System::os_name() noexcept -{ return m_system_infos.os_name; } - -std::string& System::os_id() noexcept -{ return m_system_infos.os_id; } - -std::string& System::os_versionid() noexcept -{ return m_system_infos.os_version_id; } - -std::string& System::os_version_codename() noexcept -{ return m_system_infos.os_version_codename; } - -std::string& System::host_modelname() noexcept -{ return m_system_infos.host_modelname; } - -std::string& System::host_vendor() noexcept -{ return m_system_infos.host_vendor; } - -std::string& System::host_version() noexcept -{ return m_system_infos.host_version; } - -// clang-format on -std::string& System::os_initsys_name() -{ - static bool done = false; - if (done && !is_live_mode) - return m_system_infos.os_initsys_name; - - // there's no way PID 1 doesn't exist. - // This will always succeed (because we are on linux) - std::ifstream f_initsys("/proc/1/comm", std::ios::binary); - if (!f_initsys.is_open()) - die(_("/proc/1/comm doesn't exist! (what?)")); - - std::string initsys; - std::getline(f_initsys, initsys); - size_t pos = 0; - - if ((pos = initsys.find('\0')) != std::string::npos) - initsys.erase(pos); - - if ((pos = initsys.rfind('/')) != std::string::npos) - initsys.erase(0, pos + 1); - - m_system_infos.os_initsys_name = initsys; - - done = true; - - return m_system_infos.os_initsys_name; -} - -std::string& System::os_initsys_version() -{ - static bool done = false; - if (done && !is_live_mode) - return m_system_infos.os_initsys_version; - - std::string path; - char buf[PATH_MAX]; - if (realpath(which("init").c_str(), buf)) - path = buf; - - std::ifstream f(path, std::ios::in); - std::string line; - - const std::string& name = str_tolower(this->os_initsys_name()); - switch (fnv1a16::hash(name)) - { - case "systemd"_fnv1a16: - case "systemctl"_fnv1a16: - { - while (read_binary_file(f, line)) - { - if (hasEnding(line, "running in %ssystem mode (%s)")) - { - m_system_infos.os_initsys_version = line.substr("systemd "_len); - m_system_infos.os_initsys_version.erase(m_system_infos.os_initsys_version.find(' ')); - break; - } - } - } - break; - case "openrc"_fnv1a16: - { - std::string tmp; - while(read_binary_file(f, line)) - { - if (line == "RC_VERSION") - { - m_system_infos.os_initsys_version = tmp; - break; - } - tmp = line; - } - } - break; - } - done = true; - return m_system_infos.os_initsys_version; -} - -std::string& System::pkgs_installed(const Config& config) -{ - static bool done = false; - if (!done || is_live_mode) - { - m_system_infos.pkgs_installed = get_all_pkgs(config); - done = true; - } - - return m_system_infos.pkgs_installed; -} - -#endif diff --git a/src/query/linux/theme.cpp b/src/query/linux/theme.cpp deleted file mode 100644 index b4691d18..00000000 --- a/src/query/linux/theme.cpp +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <algorithm> -#include <cstdint> - -#include "config.hpp" -#include "fmt/format.h" -#include "parse.hpp" -#include "query.hpp" -#include "rapidxml-1.13/rapidxml.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" - -#if USE_DCONF -# include <client/dconf-client.h> -# include <glib/gvariant.h> -#endif - -using namespace Query; - -const std::string& configDir = getHomeConfigDir(); - -static bool get_xsettings_xfce4(const std::string_view property, const std::string_view subproperty, std::string& ret) -{ - static bool done = false; - static rapidxml::xml_document<> doc; - - if (!done) - { - const std::string& path = configDir + "/xfce4/xfconf/xfce-perchannel-xml/xsettings.xml"; - std::ifstream f(path, std::ios::in); - if (!f.is_open()) - return false; - - std::string buffer((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); - buffer.push_back('\0'); - - doc.parse<0>(&buffer[0]); - done = true; - } - - rapidxml::xml_node<>* node1 = doc.first_node("channel")->first_node("property"); - for (; node1 && std::string_view(node1->first_attribute("name")->value()) != property; node1 = node1->next_sibling("property")); - - rapidxml::xml_node<>* node2 = node1->first_node("property"); - for (; node2; node2 = node2->next_sibling()) - { - if (std::string_view(node2->first_attribute("name")->value()) == subproperty && - node2->first_attribute("value")) - { - ret = node2->first_attribute("value")->value(); - return true; - } - } - - return false; -} - -// -// -// 1. Cursor -// -static bool assert_cursor(Theme::Theme_t& theme) -{ - return - (theme.cursor != MAGIC_LINE && theme.cursor_size != UNKNOWN) || - (!theme.cursor.empty() && !theme.cursor_size.empty()); -} - -static bool get_cursor_xresources(Theme::Theme_t& theme) -{ - const std::string& path = expandVar("~/.Xresources"); - std::ifstream f(path, std::ios::in); - if (!f.is_open()) - { - theme.cursor = MAGIC_LINE; - theme.cursor_size = UNKNOWN; - return false; - } - - std::uint16_t iter_index = 0; - std::string line; - while (std::getline(f, line) && iter_index < 2) - { - if (hasStart(line, "Xcursor.theme:")) - { - getFileValue(iter_index, line, theme.cursor, "Xcursor.theme:"_len); - strip(theme.cursor); - } - - else if (hasStart(line, "Xcursor.size:")) - { - getFileValue(iter_index, line, theme.cursor_size, "Xcursor.size:"_len); - strip(theme.cursor_size); - } - } - - return assert_cursor(theme); -} - -static bool get_cursor_dconf(const std::string_view de_name, Theme::Theme_t& theme) -{ -#if USE_DCONF - - LOAD_LIBRARY("libdconf.so", return false); - LOAD_LIB_SYMBOL(DConfClient *, dconf_client_new, void); - LOAD_LIB_SYMBOL(GVariant *, dconf_client_read, DConfClient *, const char *); - LOAD_LIB_SYMBOL(const gchar *, g_variant_get_string, GVariant *, gsize *); - LOAD_LIB_SYMBOL(gint32, g_variant_get_int32, GVariant *); - - debug("calling {}", __PRETTY_FUNCTION__); - DConfClient *client = dconf_client_new(); - GVariant *variant; - - std::string interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "/org/cinnamon/desktop/interface/"; break; - case "mate"_fnv1a16: interface = "/org/mate/interface/"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "/org/gnome/desktop/interface/"; - } - - - variant = dconf_client_read(client, (interface + "cursor-theme").c_str()); - if (variant) - theme.cursor = g_variant_get_string(variant, NULL); - - - - variant = dconf_client_read(client, (interface + "cursor-size").c_str()); - if (variant) - theme.cursor_size = fmt::to_string(g_variant_get_int32(variant)); - - return assert_cursor(theme); -#else - return false; -#endif -} - -static bool get_cursor_gsettings(const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - debug("calling {}", __PRETTY_FUNCTION__); - if (get_cursor_dconf(de_name, theme)) - return true; - - if (config.slow_query_warnings) - { - warn(_("customfetch could not detect a gtk configuration file. customfetch will use the much-slower gsettings.")); - warn(_("If there's a file in a standard location that we aren't detecting, please file an issue on our GitHub.")); - info("You can disable this warning by disabling slow-query-warnings in your config.toml file."); - } - - const char* interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "org.cinnamon.desktop.interface"; break; - case "mate"_fnv1a16: interface = "org.mate.interface"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "org.gnome.desktop.interface"; - } - - if (theme.cursor == MAGIC_LINE || theme.cursor.empty()) - { - theme.cursor.clear(); - read_exec({ "gsettings", "get", interface, "cursor-theme" }, theme.cursor); - theme.cursor.erase(std::remove(theme.cursor.begin(), theme.cursor.end(), '\''), theme.cursor.end()); - } - - if (theme.cursor_size == UNKNOWN || theme.cursor_size.empty()) - { - theme.cursor_size.clear(); - read_exec({ "gsettings", "get", interface, "cursor-size" }, theme.cursor_size); - theme.cursor_size.erase(std::remove(theme.cursor_size.begin(), theme.cursor_size.end(), '\''), theme.cursor_size.end()); - } - - return assert_cursor(theme); -} - -static bool get_gtk_cursor_config(const std::string_view path, Theme::Theme_t& theme) -{ - std::ifstream f(path.data(), std::ios::in); - if (!f.is_open()) - return false; - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(f, line) && iter_index < 2) - { - if (hasStart(line, "gtk-cursor-theme-name=")) - getFileValue(iter_index, line, theme.cursor, "gtk-cursor-theme-name="_len); - - else if (hasStart(line, "gtk-cursor-theme-size=")) - getFileValue(iter_index, line, theme.cursor_size, "gtk-cursor-theme-size="_len); - } - - return assert_cursor(theme); -} - -static bool get_cursor_from_gtk_configs(const std::uint8_t ver, Theme::Theme_t& theme) -{ - if (get_gtk_cursor_config(fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/gtkrc-{}.0", configDir, ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), theme)) - return true; - - if (get_gtk_cursor_config(fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver), theme)) - return true; - - return false; -} - -static bool get_de_cursor(const std::string_view de_name, Theme::Theme_t& theme) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - { - debug("calling {} and getting info on xfce4", __PRETTY_FUNCTION__); - get_xsettings_xfce4("Gtk", "CursorThemeName", theme.cursor); - get_xsettings_xfce4("Gtk", "CursorThemeSize", theme.cursor_size); - - return assert_cursor(theme); - - } break; - } - - return false; -} - -// -// -// 2. GTK theme -// -static bool assert_gtk_theme(Theme::Theme_t& theme) -{ - return - (theme.gtk_font != MAGIC_LINE && theme.gtk_icon_theme != MAGIC_LINE && theme.gtk_theme_name != MAGIC_LINE) || - (!theme.gtk_font.empty() && !theme.gtk_theme_name.empty() && !theme.gtk_icon_theme.empty()); -} - -static bool get_gtk_theme_config(const std::string_view path, Theme::Theme_t& theme) -{ - std::ifstream f(path.data(), std::ios::in); - if (!f.is_open()) - return false; - - std::string line; - std::uint16_t iter_index = 0; - while (std::getline(f, line) && iter_index < 3) - { - if (hasStart(line, "gtk-theme-name=")) - getFileValue(iter_index, line, theme.gtk_theme_name, "gtk-theme-name="_len); - - else if (hasStart(line, "gtk-icon-theme-name=")) - getFileValue(iter_index, line, theme.gtk_icon_theme, "gtk-icon-theme-name="_len); - - else if (hasStart(line, "gtk-font-name=")) - getFileValue(iter_index, line, theme.gtk_font, "gtk-font-name="_len); - } - - return assert_gtk_theme(theme); -} - -static bool get_gtk_theme_dconf(const std::string_view de_name, Theme::Theme_t& theme) -{ -#if USE_DCONF - - LOAD_LIBRARY("libdconf.so", return false); - LOAD_LIB_SYMBOL(DConfClient *, dconf_client_new, void); - LOAD_LIB_SYMBOL(GVariant *, dconf_client_read, DConfClient *client, const char *); - LOAD_LIB_SYMBOL(const gchar *, g_variant_get_string, GVariant *value, gsize *lenght); - - debug("calling {}", __PRETTY_FUNCTION__); - DConfClient *client = dconf_client_new(); - GVariant *variant; - - std::string interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "/org/cinnamon/desktop/interface/"; break; - case "mate"_fnv1a16: interface = "/org/mate/interface/"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "/org/gnome/desktop/interface/"; - } - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - variant = dconf_client_read(client, (interface + "gtk-theme").c_str()); - if (variant) - theme.gtk_theme_name = g_variant_get_string(variant, NULL); - } - - if (theme.gtk_icon_theme == MAGIC_LINE || theme.gtk_icon_theme.empty()) - { - variant = dconf_client_read(client, (interface + "icon-theme").c_str()); - if (variant) - theme.gtk_icon_theme = g_variant_get_string(variant, NULL); - } - - if (theme.gtk_font == MAGIC_LINE || theme.gtk_font.empty()) - { - variant = dconf_client_read(client, (interface + "font-name").c_str()); - if (variant) - theme.gtk_font = g_variant_get_string(variant, NULL); - } - - return assert_gtk_theme(theme); - -#else - return false; -#endif -} - -static void get_gtk_theme_gsettings(const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - debug("calling {}", __PRETTY_FUNCTION__); - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - const char* gtk_theme_env = std::getenv("GTK_THEME"); - - if (gtk_theme_env) - theme.gtk_theme_name = gtk_theme_env; - } - - if (get_gtk_theme_dconf(de_name, theme)) - return; - - if (config.slow_query_warnings) - { - warn(_("customfetch could not detect a gtk configuration file. customfetch will use the much-slower gsettings.")); - warn(_("If there's a file in a standard location that we aren't detecting, please file an issue on our GitHub.")); - info(_("You can disable this warning by disabling slow-query-warnings in your config.toml file.")); - } - - const char* interface; - switch(fnv1a16::hash(str_tolower(de_name.data()))) - { - case "cinnamon"_fnv1a16: interface = "org.cinnamon.desktop.interface"; break; - case "mate"_fnv1a16: interface = "org.mate.interface"; break; - - case "gnome"_fnv1a16: - case "budgie"_fnv1a16: - case "unity"_fnv1a16: - default: - interface = "org.gnome.desktop.interface"; - } - - if (theme.gtk_theme_name == MAGIC_LINE || theme.gtk_theme_name.empty()) - { - theme.gtk_theme_name.clear(); - read_exec({ "gsettings", "get", interface, "gtk-theme" }, theme.gtk_theme_name); - } - - if (theme.gtk_icon_theme == MAGIC_LINE || theme.gtk_icon_theme.empty()) - { - theme.gtk_icon_theme.clear(); - read_exec({ "gsettings", "get", interface, "icon-theme" }, theme.gtk_icon_theme); - } - - if (theme.gtk_font == MAGIC_LINE || theme.gtk_font.empty()) - { - theme.gtk_font.clear(); - read_exec({ "gsettings", "get", interface, "font-name" }, theme.gtk_font); - } - - theme.gtk_theme_name.erase(std::remove(theme.gtk_theme_name.begin(), theme.gtk_theme_name.end(), '\''), theme.gtk_theme_name.end()); - theme.gtk_icon_theme.erase(std::remove(theme.gtk_icon_theme.begin(), theme.gtk_icon_theme.end(), '\''), theme.gtk_icon_theme.end()); - theme.gtk_font.erase(std::remove(theme.gtk_font.begin(), theme.gtk_font.end(), '\''), theme.gtk_font.end()); -} - -static void get_gtk_theme_from_configs(const std::uint8_t ver, const std::string_view de_name, Theme::Theme_t& theme, const Config& config) -{ - if (get_gtk_theme_config(fmt::format("{}/gtk-{}.0/settings.ini", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/gtk-{}.0/gtkrc", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/gtkrc-{}.0", configDir, ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0", std::getenv("HOME"), ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0-kde", std::getenv("HOME"), ver), theme)) - return; - - if (get_gtk_theme_config(fmt::format("{}/.gtkrc-{}.0-kde4", std::getenv("HOME"), ver), theme)) - return; - - get_gtk_theme_gsettings(de_name, theme, config); -} - -static void get_de_gtk_theme(const std::string_view de_name, const std::uint8_t ver, Theme::Theme_t& theme, const Config& config) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: - { - debug("calling {} and getting info on xfce4", __PRETTY_FUNCTION__); - get_xsettings_xfce4("Net", "ThemeName", theme.gtk_theme_name); - get_xsettings_xfce4("Net", "IconThemeName", theme.gtk_icon_theme); - get_xsettings_xfce4("Gtk", "FontName", theme.gtk_font); - - if (!assert_gtk_theme(theme)) - get_gtk_theme_from_configs(ver, de_name, theme, config); - - } break; - - default: - get_gtk_theme_from_configs(ver, de_name, theme, config); - } -} - -static void get_gtk_theme(const bool dont_query_dewm, const std::uint8_t ver, const std::string_view de_name, - Theme::Theme_t& theme, const Config& config, const bool gsettings_only) -{ - if (gsettings_only) - get_gtk_theme_gsettings(de_name, theme, config); - else if (dont_query_dewm) - get_gtk_theme_from_configs(ver, de_name, theme, config); - else - get_de_gtk_theme(de_name, ver, theme, config); -} - -// clang-format off -Theme::Theme(const std::uint8_t ver, systemInfo_t& queried_themes, - const std::string& theme_name_version, const Config& config, const bool gsettings_only) - : m_theme_name(theme_name_version), - m_queried_themes(queried_themes) -{ - if (queried_themes.find(theme_name_version) != queried_themes.end()) - return; - - const std::string& wm_name = query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name()); - const std::string& de_name = query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), wm_name); - - if (((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && - de_name == wm_name) || de_name == MAGIC_LINE) - m_wmde_name = wm_name; - else - m_wmde_name = de_name; - - get_gtk_theme(query_user.m_bDont_query_dewm, ver, m_wmde_name, m_theme_infos, config, gsettings_only); - - if (m_theme_infos.gtk_theme_name.empty()) - m_theme_infos.gtk_theme_name = MAGIC_LINE; - - if (m_theme_infos.gtk_font.empty()) - m_theme_infos.gtk_font = MAGIC_LINE; - - if (m_theme_infos.gtk_icon_theme.empty()) - m_theme_infos.gtk_icon_theme = MAGIC_LINE; - - m_queried_themes.insert( - {m_theme_name, { - {"theme-name", variant(m_theme_infos.gtk_theme_name)}, - {"icon-theme-name", variant(m_theme_infos.gtk_icon_theme)}, - {"font-name", variant(m_theme_infos.gtk_font)}, - }} - ); -} - -// only use it for cursor -Theme::Theme(systemInfo_t& queried_themes, const Config& config, const bool gsettings_only) : m_queried_themes(queried_themes) -{ - const std::string& wm_name = query_user.wm_name(query_user.m_bDont_query_dewm, query_user.term_name()); - const std::string& de_name = query_user.de_name(query_user.m_bDont_query_dewm, query_user.term_name(), wm_name); - - if (((de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && - de_name == wm_name) || de_name == MAGIC_LINE) - m_wmde_name = wm_name; - else - m_wmde_name = de_name; - - if (gsettings_only) { get_cursor_gsettings(m_wmde_name, m_theme_infos, config); } - else if (get_de_cursor(m_wmde_name, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(4, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(3, m_theme_infos)){} - else if (get_cursor_from_gtk_configs(2, m_theme_infos)){} - else if (get_cursor_xresources(m_theme_infos)){} - else get_cursor_gsettings(m_wmde_name, m_theme_infos, config); - - if (m_theme_infos.cursor.empty()) - m_theme_infos.cursor = MAGIC_LINE; - else - { - size_t pos = 0; - if ((pos = m_theme_infos.cursor.rfind("cursor")) != std::string::npos) - m_theme_infos.cursor.erase(pos); - - if ((pos = m_theme_infos.cursor.rfind('_')) != std::string::npos) - m_theme_infos.cursor.erase(pos - 1); - - } - -} - -std::string Theme::gtk_theme() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "theme-name"); } - -std::string Theme::gtk_icon_theme() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "icon-theme-name"); } - -std::string Theme::gtk_font() noexcept -{ return getInfoFromName(m_queried_themes, m_theme_name, "font-name"); } - -std::string& Theme::cursor() noexcept -{ return m_theme_infos.cursor; } - -std::string& Theme::cursor_size() noexcept -{ return m_theme_infos.cursor_size; } - -#endif // CF_LINUX diff --git a/src/query/linux/user.cpp b/src/query/linux/user.cpp deleted file mode 100644 index 59f36deb..00000000 --- a/src/query/linux/user.cpp +++ /dev/null @@ -1,539 +0,0 @@ -/* - * Copyright 2024 Toni500git - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the - * following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following - * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include "platform.hpp" -#if CF_LINUX - -#include <dlfcn.h> -#include <unistd.h> - -#include <cctype> -#include <cstdlib> -#include <cstring> -#include <filesystem> -#include <fstream> -#include <string> - -#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) -#include <sys/socket.h> -#include <wayland-client.h> -#endif - -// #if __has_include(<sys/socket.h>) && __has_include(<X11/Xlib.h>) -// # include <X11/Xlib.h> -// #endif - -#include "query.hpp" -#include "switch_fnv1a.hpp" -#include "util.hpp" -#include "utils/dewm.hpp" -#include "utils/term.hpp" - -using namespace Query; - -static std::string get_de_name() -{ - std::string ret = parse_de_env(); - debug("get_de_name = {}", ret); - if (hasStart(ret, "X-")) - ret.erase(0,2); - - return ret; -} - -static std::string get_wm_name(std::string& wm_path_exec) -{ - std::string path, proc_name, wm_name; - const uid_t uid = getuid(); - - for (auto const& dir_entry : std::filesystem::directory_iterator{ "/proc/" }) - { - if (!std::isdigit((dir_entry.path().string().at(6)))) // /proc/5 - continue; - - path = dir_entry.path() / "loginuid"; - std::ifstream f_uid(path, std::ios::binary); - std::string s_uid; - std::getline(f_uid, s_uid); - if (std::stoul(s_uid) != uid) - continue; - - path = dir_entry.path() / "cmdline"; - std::ifstream f_cmdline(path, std::ios::binary); - std::getline(f_cmdline, proc_name); - - size_t pos = 0; - if ((pos = proc_name.find('\0')) != std::string::npos) - proc_name.erase(pos); - - if ((pos = proc_name.rfind('/')) != std::string::npos) - proc_name.erase(0, pos + 1); - - debug("WM proc_name = {}", proc_name); - - if ((wm_name = prettify_wm_name(proc_name)) == MAGIC_LINE) - continue; - - char buf[PATH_MAX]; - wm_path_exec = realpath((dir_entry.path().string() + "/exe").c_str(), buf); - break; - } - - debug("wm_name = {}", wm_name); - if (wm_name.empty()) - return MAGIC_LINE; - - return wm_name; -} - -static std::string get_de_version(const std::string_view de_name) -{ - switch (fnv1a16::hash(str_tolower(de_name.data()))) - { - case "mate"_fnv1a16: return get_mate_version(); - case "cinnamon"_fnv1a16: return get_cinnamon_version(); - - case "kde"_fnv1a16: return get_kwin_version(); - - case "xfce"_fnv1a16: - case "xfce4"_fnv1a16: return get_xfce4_version(); - - case "gnome"_fnv1a16: - case "gnome-shell"_fnv1a16: - { - std::string ret; - read_exec({ "gnome-shell", "--version" }, ret); - ret.erase(0, ret.rfind(' ')); - return ret; - } - default: - { - std::string ret; - read_exec({ de_name.data(), "--version" }, ret); - ret.erase(0, ret.rfind(' ')); - return ret; - } - } -} - -static std::string get_wm_wayland_name(std::string& wm_path_exec) -{ -#if __has_include(<sys/socket.h>) && __has_include(<wayland-client.h>) - LOAD_LIBRARY("libwayland-client.so", return get_wm_name(wm_path_exec);) - - LOAD_LIB_SYMBOL(wl_display*, wl_display_connect, const char* name) - LOAD_LIB_SYMBOL(void, wl_display_disconnect, wl_display* display) - LOAD_LIB_SYMBOL(int, wl_display_get_fd, wl_display* display) - - std::string ret = MAGIC_LINE; - - struct wl_display* display = wl_display_connect(NULL); - - struct ucred ucred; - socklen_t len = sizeof(struct ucred); - if (getsockopt(wl_display_get_fd(display), SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1) - return MAGIC_LINE; - - std::ifstream f(fmt::format("/proc/{}/comm", ucred.pid), std::ios::in); - f >> ret; - wl_display_disconnect(display); - - char buf[PATH_MAX]; - wm_path_exec = realpath(fmt::format("/proc/{}/exe", ucred.pid).c_str(), buf); - - UNLOAD_LIBRARY() - - return prettify_wm_name(ret); -#else - return get_wm_name(wm_path_exec); -#endif -} - -static std::string get_shell_version(const std::string_view shell_name) -{ - std::string ret; - - if (shell_name == "nu") - ret = read_shell_exec("nu -c \"version | get version\""); - else - ret = read_shell_exec(fmt::format("{} -c 'echo \"${}_VERSION\"'", shell_name, str_toupper(shell_name.data()))); - - strip(ret); - return ret; -} - -static std::string get_shell_name(const std::string_view shell_path) -{ - return shell_path.substr(shell_path.rfind('/') + 1).data(); -} - -static std::string get_term_name(std::string& term_ver, const std::string_view osname) -{ - // customfetch -> shell -> terminal - const pid_t ppid = getppid(); - std::ifstream ppid_f(fmt::format("/proc/{}/status", ppid), std::ios::in); - std::string line, term_pid; - while (std::getline(ppid_f, line)) - { - if (hasStart(line, "PPid:")) - { - term_pid = line.substr("PPid:"_len); - strip(term_pid); - break; - } - } - debug("term_pid = {}", term_pid); - - if (std::stoi(term_pid) < 1) - return MAGIC_LINE; - - std::ifstream f("/proc/" + term_pid + "/comm", std::ios::in); - std::string term_name; - std::getline(f, term_name); - - // st (suckless terminal) - if (term_name == "exe") - term_name = "st"; - - // either gnome-console or "gnome-terminal-" - // I hope this is not super stupid - else if (hasStart(term_name, "gnome")) - { - if (hasStart(term_name, "gnome-console")) - term_name.erase("gnome-console"_len + 1); - else if (hasStart(term_name, "gnome-terminal")) - term_name.erase("gnome-terminal"_len + 1); - } - - // let's try to get the real terminal name - // on NixOS, instead of returning the -wrapped name. - // tested on gnome-console, kitty, st and alacritty - // hope now NixOS users will know the terminal they got, along the version if possible - if (osname.find("NixOS") != osname.npos || - (hasEnding(term_name, "wrapped") && which("nix") != UNKNOWN)) - { - // /nix/store/sha256string-gnome-console-0.31.0/bin/.kgx-wrapped - char buf[PATH_MAX]; - std::string tmp_name = realpath(("/proc/" + term_pid + "/exe").c_str(), buf); - - size_t pos; - if ((pos = tmp_name.find('-')) != std::string::npos) - tmp_name.erase(0, pos + 1); // gnome-console-0.31.0/bin/.kgx-wrapped - - if ((pos = tmp_name.find('/')) != std::string::npos) - tmp_name.erase(pos); // gnome-console-0.31.0 - - if ((pos = tmp_name.rfind('-')) != std::string::npos) - { - term_ver = tmp_name.substr(pos+1); - tmp_name.erase(pos); // gnome-console EZ - } - - term_name = tmp_name; - } - - return term_name; -} - -static std::string get_term_version(std::string_view term_name) -{ - if (term_name.empty()) - return UNKNOWN; - - bool remove_term_name = true; - std::string ret; - - if (hasStart(term_name, "kitty")) - term_name = "kitten"; - - switch (fnv1a16::hash(str_tolower(term_name.data()))) - { - case "st"_fnv1a16: - if (fast_detect_st_ver(ret)) - remove_term_name = false; - break; - - case "konsole"_fnv1a16: - if (fast_detect_konsole_ver(ret)) - remove_term_name = false; - break; - - case "xterm"_fnv1a16: - get_term_version_exec(term_name, ret, true); break; - - default: - get_term_version_exec(term_name, ret); - } - - - debug("get_term_version ret = {}", ret); - - if (ret.empty()) - return UNKNOWN; - - if (hasStart(ret, "# GNOME")) - { - if (hasStart(ret, "# GNOME Console ")) - ret.erase(0, "# GNOME Console"_len); - else if (hasStart(ret, "# GNOME Terminal ")) - ret.erase(0, "# GNOME Terminal "_len); - debug("gnome ret = {}", ret); - remove_term_name = false; - } - // Xterm(388) - else if (term_name == "xterm") - { - ret.erase(0, term_name.length() + 1); // 388) - ret.pop_back(); // 388 - return ret; - } - - if (remove_term_name) - ret.erase(0, term_name.length() + 1); - - const size_t pos = ret.find(' '); - if (pos != std::string::npos) - ret.erase(pos); - - debug("get_term_version ret after = {}", ret); - return ret; -} - -User::User() noexcept -{ - CHECK_INIT(!m_bInit) - { - const uid_t uid = getuid(); - - if (m_pPwd = getpwuid(uid), !m_pPwd) - die(_("getpwent failed: {}\nCould not get user infos"), std::strerror(errno)); - - m_bInit = true; - } -} - -// clang-format off -std::string User::name() noexcept -{ return m_pPwd->pw_name; } - -std::string User::shell_path() noexcept -{ return m_pPwd->pw_shell; } - -// clang-format on -// Be ready to loose some brain cells from now on -std::string& User::shell_name() noexcept -{ - static bool done = false; - if (!done) - { - m_users_infos.shell_name = get_shell_name(this->shell_path()); - done = true; - } - - return m_users_infos.shell_name; -} - -std::string& User::shell_version(const std::string_view shell_name) -{ - if (m_users_infos.shell_name.empty()) - { - m_users_infos.shell_version = UNKNOWN; - return m_users_infos.shell_version; - } - - static bool done = false; - if (!done) - { - m_users_infos.shell_version = get_shell_version(shell_name); - done = true; - } - - return m_users_infos.shell_version; -} - -std::string& User::wm_name(bool dont_query_dewm, const std::string_view term_name) -{ - if (dont_query_dewm || hasStart(term_name, "/dev")) - { - m_users_infos.wm_name = MAGIC_LINE; - return m_users_infos.wm_name; - } - - static bool done = false; - debug("CALLING {} || done = {} && de_name = {} && wm_name = {}", __func__, done, m_users_infos.de_name, - m_users_infos.wm_name); - - if (!done) - { - const char* env = std::getenv("WAYLAND_DISPLAY"); - if (env != nullptr && env[0] != '\0') - m_users_infos.wm_name = get_wm_wayland_name(m_users_infos.m_wm_path); - else - m_users_infos.wm_name = get_wm_name(m_users_infos.m_wm_path); - - if (m_users_infos.de_name == m_users_infos.wm_name) - m_users_infos.de_name = MAGIC_LINE; - - done = true; - } - - return m_users_infos.wm_name; -} - -std::string& User::wm_version(bool dont_query_dewm, const std::string_view term_name) -{ - if (dont_query_dewm || hasStart(term_name, "/dev")) - { - m_users_infos.wm_name = MAGIC_LINE; - return m_users_infos.wm_name; - } - - static bool done = false; - if (!done) - { - m_users_infos.wm_version.clear(); - if (m_users_infos.wm_name == "Xfwm4" && get_fast_xfwm4_version(m_users_infos.wm_version, m_users_infos.m_wm_path)) - { - done = true; - goto _return; - } - - if (m_users_infos.wm_name == "dwm") - read_exec({m_users_infos.m_wm_path.c_str(), "-v"}, m_users_infos.wm_version, true); - else - read_exec({m_users_infos.m_wm_path.c_str(), "--version"}, m_users_infos.wm_version); - - if (m_users_infos.wm_name == "Xfwm4") - m_users_infos.wm_version.erase(0, "\tThis is xfwm4 version "_len); // saying only "xfwm4 4.18.2 etc." no? - else - m_users_infos.wm_version.erase(0, m_users_infos.wm_name.length() + 1); - - const size_t pos = m_users_infos.wm_version.find(' '); - if (pos != std::string::npos) - m_users_infos.wm_version.erase(pos); - - done = true; - } - - _return: - return m_users_infos.wm_version; -} - -std::string& User::de_name(bool dont_query_dewm, const std::string_view term_name, const std::string_view wm_name) -{ - // first let's see if we are not in a tty or if the user doesn't want to - // if so don't even try to get the DE or WM names - // they waste times - if (dont_query_dewm || hasStart(term_name, "/dev")) - { - m_users_infos.de_name = MAGIC_LINE; - return m_users_infos.de_name; - } - - static bool done = false; - debug("CALLING {} || done = {} && de_name = {} && wm_name = {}", __func__, done, m_users_infos.de_name, - m_users_infos.wm_name); - - if (!done) - { - if ((m_users_infos.de_name != MAGIC_LINE && wm_name != MAGIC_LINE) && - m_users_infos.de_name == wm_name) - { - m_users_infos.de_name = MAGIC_LINE; - done = true; - return m_users_infos.de_name; - } - - m_users_infos.de_name = get_de_name(); - if (m_users_infos.de_name == m_users_infos.wm_name) - m_users_infos.de_name = MAGIC_LINE; - - done = true; - } - - return m_users_infos.de_name; -} - -std::string& User::de_version(const std::string_view de_name) -{ - if (m_bDont_query_dewm || de_name == UNKNOWN || de_name == MAGIC_LINE || de_name.empty()) - { - m_users_infos.de_version = UNKNOWN; - return m_users_infos.de_version; - } - - static bool done = false; - if (!done) - { - m_users_infos.de_version = get_de_version(str_tolower(de_name.data())); - done = true; - } - - return m_users_infos.de_version; -} - -std::string& User::term_name() -{ - static bool done = false; - if (done || is_live_mode) - return m_users_infos.term_name; - - Query::System query_sys; - m_users_infos.term_name = get_term_name(m_users_infos.term_version, query_sys.os_name()); - if (hasStart(str_tolower(m_users_infos.term_name), "login") || hasStart(m_users_infos.term_name, "init") || - hasStart(m_users_infos.term_name, "(init)")) - { - m_users_infos.term_name = ttyname(STDIN_FILENO); - m_users_infos.term_version = "NO VERSIONS ABOSULETY"; // lets not make it unknown - m_bDont_query_dewm = true; - } - - done = true; - - return m_users_infos.term_name; -} - -std::string& User::term_version(const std::string_view term_name) -{ - static bool done = false; - if (done || is_live_mode) - return m_users_infos.term_version; - - if (m_users_infos.term_version == "NO VERSIONS ABOSULETY") - { - m_users_infos.term_version.clear(); - goto done; - } - else if (m_users_infos.term_version != MAGIC_LINE) - goto done; - - m_users_infos.term_version = get_term_version(term_name); -done: - done = true; - - return m_users_infos.term_version; -} - -#endif // CF_LINUX diff --git a/src/query/linux/utils/term.hpp b/src/query/linux/utils/term.hpp deleted file mode 100644 index 6365a407..00000000 --- a/src/query/linux/utils/term.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef _TERM_HPP -#define _TERM_HPP - -#include <string> - -void get_term_version_exec(const std::string_view term, std::string& ret, bool _short = false, bool _stderr = false); - -bool fast_detect_konsole_ver(std::string& ret); -bool fast_detect_st_ver(std::string& ret); - -#endif // _TERM_HPP diff --git a/src/toml++/Makefile b/src/toml++/Makefile deleted file mode 100644 index d196ae7b..00000000 --- a/src/toml++/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -CXX ?= g++ -SRC = toml.cpp -TARGET = toml.o -CXXFLAGS = -I../../include -std=c++20 - -all: $(TARGET) - -$(TARGET): - ${CXX} $(SRC) $(CXXFLAGS) -c -o ../../$(BUILDDIR)/$@ - -clean: - rm -rf ../../$(BUILDDIR)/toml++/$(TARGET) - -.PHONY: $(TARGET) clean all diff --git a/src/util.cpp b/src/util.cpp index fae54d67..2824352e 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,25 +1,25 @@ /* - * Copyright 2024 Toni500git - * + * Copyright 2025 Toni500git + * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the * following conditions are met: - * + * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following * disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided with the distribution. - * - * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products - * derived from this software without specific prior written permission. - * + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, - * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ @@ -32,23 +32,27 @@ #include <algorithm> #include <array> -#include <cerrno> #include <cstdint> #include <cstring> #include <filesystem> #include <fstream> #include <iostream> -#include <memory> #include <sstream> #include <string> #include <string_view> -#include <tuple> #include <vector> #include "fmt/color.h" #include "fmt/ranges.h" #include "pci.ids.hpp" #include "platform.hpp" +#include "tiny-process-library/process.hpp" + +#if !CF_ANDROID +const std::string& all_ids = get_pci_ids(); +#else +const std::string& all_ids = ""; +#endif bool hasEnding(const std::string_view fullString, const std::string_view ending) { @@ -88,7 +92,6 @@ std::string expandVar(std::string ret, bool dont) if (ret.empty() || dont) return ret; -#if !ANDROID_APP const char* env; if (ret.front() == '~') { @@ -117,7 +120,6 @@ std::string expandVar(std::string ret, bool dont) ret = env; ret += temp; } -#endif return ret; } @@ -135,18 +137,18 @@ std::string read_by_syspath(const std::string_view path, bool report_error) std::string result; std::getline(f, result); - + if (!result.empty() && result.back() == '\n') result.pop_back(); return result; } -byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix) +byte_units_t auto_divide_bytes(const double num, const std::uint16_t base, const std::string_view maxprefix) { double size = num; - std::array<std::string_view, 10> prefixes; + std::array<std::string_view, 9> prefixes; if (base == 1024) prefixes = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; else if (base == 1000) @@ -154,34 +156,44 @@ byte_units_t auto_devide_bytes(const double num, const std::uint16_t base, const else prefixes = { "B" }; - std::uint16_t counter = 0; - if (maxprefix.empty()) - { - for (; counter < prefixes.size() && size >= base; ++counter) - size /= base; - } - else + size_t counter = 0; + const auto& max_it = !maxprefix.empty() + ? std::find(prefixes.begin(), prefixes.end(), maxprefix) + : prefixes.end(); + + while (counter + 1 < prefixes.size() && size >= base) { - for (; counter < prefixes.size() && size >= base && prefixes.at(counter) != maxprefix; ++counter) - size /= base; + if (max_it != prefixes.end() && prefixes[counter] == maxprefix) + break; + size /= base; + ++counter; } - return { prefixes.at(counter).data(), size }; + return { prefixes[counter].data(), size }; } -byte_units_t devide_bytes(const double num, const std::string_view prefix) +byte_units_t divide_bytes(const double num, const std::string_view prefix) { - if (prefix != "B") - { - // GiB - // 012 - if (prefix.size() == 3 && prefix.at(1) == 'i') - return auto_devide_bytes(num, 1024, prefix); - else - return auto_devide_bytes(num, 1000, prefix); - } + if (prefix == "B") + return { "B", num }; + + // GiB + // 012 + const std::uint16_t base = (prefix.size() == 3 && prefix[1] == 'i') ? 1024 : 1000; + std::array<std::string_view, 9> prefixes; + if (base == 1024) + prefixes = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB" }; + else if (base == 1000) + prefixes = { "B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + + const auto& it = std::find(prefixes.begin(), prefixes.end(), prefix); + if (it == prefixes.end()) + return { "B", num }; + + const size_t index = std::distance(prefixes.begin(), it); + const double value = num / std::pow(static_cast<double>(base), index); - return auto_devide_bytes(num, 0); + return { prefix.data(), value }; } bool is_file_image(const unsigned char* bytes) @@ -209,31 +221,22 @@ bool is_file_image(const unsigned char* bytes) // clang-format on } -void strip(std::string& input) +void strip(std::string& input, bool padding_only) { if (input.empty()) - { return; - } - // optimization for input size == 1 - if (input.size() == 1) + if (padding_only) { - if (input.at(0) == ' ' || input.at(0) == '\t' || input.at(0) == '\n') - { - input = ""; - return; - } - else - { - return; - } + const char* ws = " \t\n\r\f\v"; + input.erase(input.find_last_not_of(ws) + 1); + input.erase(0, input.find_first_not_of(ws)); + } + else + { + input.erase(std::remove_if(input.begin(), input.end(), [](unsigned char c) { return std::isspace(c); }), + input.end()); } - - // https://stackoverflow.com/a/25385766 - const char* ws = " \t\n\r\f\v"; - input.erase(input.find_last_not_of(ws) + 1); - input.erase(0, input.find_first_not_of(ws)); } void getFileValue(u_short& iterIndex, const std::string_view line, std::string& str, const size_t& amount) @@ -259,7 +262,7 @@ std::string shorten_vendor_name(std::string vendor) fmt::rgb hexStringToColor(const std::string_view hexstr) { std::stringstream ss; - ss << std::hex << hexstr.substr(1).data(); + ss << std::hex << ((hexstr[0] == '#') ? hexstr.substr(1).data() : hexstr.data()); uint value; ss >> value; @@ -296,15 +299,15 @@ bool read_binary_file(std::ifstream& f, std::string& ret) std::string get_relative_path(const std::string_view relative_path, const std::string_view env, const long long mode) { - const char* _env = std::getenv(env.data()); - if (!_env) + const char* c_env = std::getenv(env.data()); + if (!c_env) return UNKNOWN; struct stat sb; std::string fullPath; fullPath.reserve(1024); - for (const std::string& dir : split(_env, ':')) + for (const std::string& dir : split(c_env, ':')) { // -300ns for not creating a string. stonks fullPath += dir; @@ -357,107 +360,28 @@ void replace_str(std::string& str, const std::string_view from, const std::strin } } -bool read_exec(std::vector<const char*> cmd, std::string& output, bool useStdErr, bool noerror_print) +bool read_exec(std::vector<std::string> cmd, std::string& output, bool useStdErr, bool noerror_print) { debug("{} cmd = {}", __func__, cmd); - std::array<int, 2> pipeout; - - if (pipe(pipeout.data()) < 0) - die(_("pipe() failed: {}"), strerror(errno)); - - const pid_t pid = fork(); - - // we wait for the command to finish then start executing the rest - if (pid > 0) - { - close(pipeout.at(1)); - - int status; - waitpid(pid, &status, 0); // Wait for the child to finish - - if (WIFEXITED(status) && (WEXITSTATUS(status) == 0 || useStdErr)) - { - // read stdout - debug("reading stdout"); - char c; - while (read(pipeout.at(0), &c, 1) == 1) - output += c; - - close(pipeout.at(0)); - if (!output.empty() && output.back() == '\n') - output.pop_back(); - - return true; - } - else - { - if (!noerror_print) + TinyProcessLib::Process proc( + cmd, "", + [&](const char* bytes, size_t n) { + if (!useStdErr) + output += std::string(bytes, n); + }, + [&](const char* bytes, size_t n) { + if (useStdErr) + output += std::string(bytes, n); + else if (!noerror_print) error(_("Failed to execute the command: {}"), fmt::join(cmd, " ")); - } - } - else if (pid == 0) - { - int nullFile = open("/dev/null", O_WRONLY | O_CLOEXEC); - dup2(pipeout.at(1), useStdErr ? STDERR_FILENO : STDOUT_FILENO); - dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO); + }); - setenv("LANG", "C", 1); - cmd.push_back(nullptr); - execvp(cmd.at(0), const_cast<char* const*>(cmd.data())); + if (!output.empty() && output.back() == '\n') + output.pop_back(); - die(_("An error has occurred with execvp: {}"), strerror(errno)); - } - else - { - close(pipeout.at(0)); - close(pipeout.at(1)); - die(_("fork() failed: {}"), strerror(errno)); - } - - close(pipeout.at(0)); - close(pipeout.at(1)); - - return false; + return proc.get_exit_status() == 0; } -bool taur_exec(const std::vector<std::string_view> cmd_str, const bool noerror_print) -{ - std::vector<const char*> cmd; - for (const std::string_view str : cmd_str) - cmd.push_back(str.data()); - - int pid = fork(); - - if (pid < 0) - { - die(_("fork() failed: {}"), strerror(errno)); - } - - if (pid == 0) - { - debug("running {}", cmd); - cmd.push_back(nullptr); - execvp(cmd.at(0), const_cast<char* const*>(cmd.data())); - - // execvp() returns instead of exiting when failed - die(_("An error has occurred: {}: {}"), cmd.at(0), strerror(errno)); - } - else if (pid > 0) - { // we wait for the command to finish then start executing the rest - int status; - waitpid(pid, &status, 0); // Wait for the child to finish - - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - return true; - else - { - if (!noerror_print) - error(_("Failed to execute the command: {}"), fmt::join(cmd, " ")); - } - } - - return false; -} std::string str_tolower(std::string str) { for (char& x : str) @@ -552,32 +476,6 @@ std::string binarySearchPCIArray(const std::string_view vendor_id_s) return vendor_from_entry(vendors_location, vendor_id); } -std::string read_shell_exec(const std::string_view cmd) -{ - std::array<char, 1024> buffer; - std::string result; - std::unique_ptr<FILE, void(*)(FILE*)> pipe(popen(cmd.data(), "r"), - [](FILE *f) -> void - { - // wrapper to ignore the return value from pclose(). - // Is needed with newer versions of gnu g++ - std::ignore = pclose(f); - }); - - if (!pipe) - die(_("popen() failed: {}"), std::strerror(errno)); - - result.reserve(buffer.size()); - while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) - result += buffer.data(); - - // why there is a '\n' at the end?? - if (!result.empty() && result.back() == '\n') - result.pop_back(); - - return result; -} - std::string name_from_entry(size_t dev_entry_pos) { dev_entry_pos += 6; // Offset from the first character to the actual name that we want (xxxx <device name>) @@ -612,16 +510,32 @@ std::string vendor_from_entry(const size_t vendor_entry_pos, const std::string_v return description.substr(first, (last - first + 1)); } -// clang-format off -#if ANDROID_APP -std::string getHomeConfigDir() +std::filesystem::path getHomeConfigDir() { - return "/storage/emulated/0/.config"; + const char* dir = std::getenv("XDG_CONFIG_HOME"); + if (dir != NULL && dir[0] != '\0' && std::filesystem::exists(dir)) + { + std::string str_dir(dir); + if (str_dir.back() == '/') + str_dir.pop_back(); + return str_dir; + } + else + { + const char* home = std::getenv("HOME"); + if (home == nullptr) + die(_("Failed to find $HOME, set it to your home directory!")); + + return std::filesystem::path(home) / ".config"; + } } -#else -std::string getHomeConfigDir() + +std::filesystem::path getConfigDir() +{ return getHomeConfigDir() / "customfetch"; } + +std::filesystem::path getHomeCacheDir() { - const char* dir = std::getenv("XDG_CONFIG_HOME"); + const char* dir = std::getenv("XDG_CACHE_HOME"); if (dir != NULL && dir[0] != '\0' && std::filesystem::exists(dir)) { std::string str_dir(dir); @@ -635,10 +549,9 @@ std::string getHomeConfigDir() if (home == nullptr) die(_("Failed to find $HOME, set it to your home directory!")); - return std::string(home) + "/.config"; + return std::filesystem::path(home) / ".cache"; } } -#endif -std::string getConfigDir() -{ return getHomeConfigDir() + "/customfetch"; } +std::filesystem::path getCacheDir() +{ return getHomeCacheDir() / "customfetch"; } diff --git a/tests/Makefile b/tests/Makefile new file mode 100644 index 00000000..b20df80e --- /dev/null +++ b/tests/Makefile @@ -0,0 +1,109 @@ +CXX ?= g++ +VARS ?= -DENABLE_NLS=1 + +DEBUG ?= 1 +GUI_APP ?= 0 +VENDOR_TEST ?= 0 +DEVICE_TEST ?= 0 + +USE_DCONF ?= 1 +# https://stackoverflow.com/a/1079861 +# WAY easier way to build debug and release builds +ifeq ($(DEBUG), 1) + BUILDDIR = build/debug + CXXFLAGS := -ggdb3 -Wall -Wextra -Wpedantic -Wno-unused-parameter -DDEBUG=1 -fsanitize=address $(DEBUG_CXXFLAGS) $(CXXFLAGS) + LDFLAGS += -fsanitize=address +else + # Check if an optimization flag is not already set + ifneq ($(filter -O%,$(CXXFLAGS)),) + $(info Keeping the existing optimization flag in CXXFLAGS) + else + CXXFLAGS := -O3 $(CXXFLAGS) + endif + BUILDDIR = build/release +endif + +ifeq ($(VENDOR_TEST), 1) + VARS += -DVENDOR_TEST=1 +endif + +ifeq ($(DEVICE_TEST), 1) + VARS += -DDEVICE_TEST=1 +endif + +ifeq ($(GUI_APP), 1) + TARGET = $(NAME)-gui + VARS += -DGUI_APP=1 + LDFLAGS += `pkg-config --libs gtkmm-3.0` + CXXFLAGS += `pkg-config --cflags gtkmm-3.0` +endif + +ifeq ($(USE_DCONF), 1) + ifeq ($(shell pkg-config --exists glib-2.0 dconf && echo 1), 1) + CXXFLAGS += -DUSE_DCONF=1 `pkg-config --cflags glib-2.0 dconf` + else + CXXFLAGS += -DUSE_DCONF=0 + endif +endif + +SRC_DIR = ../src +TEST_DIR = . + +NAME = customfetch +TARGET ?= $(NAME) +OLDVERSION = 1.0.0 +VERSION = 2.0.0-beta1 +SRC = $(filter-out $(SRC_DIR)/main.cpp, $(wildcard $(SRC_DIR)/*.cpp $(SRC_DIR)/query/linux/*.cpp $(SRC_DIR)/query/android/*.cpp $(SRC_DIR)/query/macos/*.cpp $(SRC_DIR)/query/linux/utils/*.cpp)) +OBJ = $(patsubst $(SRC_DIR)/%.cpp, $(BUILDDIR)/%.o, $(SRC)) +TESTS = $(patsubst $(TEST_DIR)/%.cpp, $(TEST_DIR)/%, $(wildcard $(TEST_DIR)/test*.cpp)) +LDFLAGS += -L./$(BUILDDIR)/fmt -lfmt -ldl +CXXFLAGS ?= -mtune=generic -march=native +CXXFLAGS += -fvisibility=hidden -I$(SRC_DIR)/../include -std=c++20 $(VARS) -DVERSION=\"$(VERSION)\" + +all: fmt toml json catch2 bin + +fmt: +ifeq ($(wildcard $(BUILDDIR)/fmt/libfmt.a),) + mkdir -p $(BUILDDIR)/fmt + make -C $(SRC_DIR)/libs/fmt BUILDDIR=tests/$(BUILDDIR)/fmt +endif + +toml: +ifeq ($(wildcard $(BUILDDIR)/toml++/toml.o),) + mkdir -p $(BUILDDIR)/toml++ + make -C $(SRC_DIR)/libs/toml++ BUILDDIR=tests/$(BUILDDIR)/toml++ +endif + +json: +ifeq ($(wildcard $(BUILDDIR)/json/json.o),) + mkdir -p $(BUILDDIR)/json + make -C $(SRC_DIR)/libs/json BUILDDIR=tests/$(BUILDDIR)/json +endif + +catch2: +#ifeq ($(wildcard $(BUILDDIR)/catch2/catch.o),) +ifneq ($(shell test -e $(BUILDDIR)/catch2/catch.o && echo -n yes), yes) + mkdir -p $(BUILDDIR)/catch2 + $(CXX) -std=c++20 catch2/catch_amalgamated.cpp -c -o $(BUILDDIR)/catch2/catch.o +endif + +locale: + scripts/make_mo.sh locale/ + +$(BUILDDIR)/%.o: $(SRC_DIR)/%.cpp + mkdir -p $(BUILDDIR)/query/linux/utils $(BUILDDIR)/query/macos $(BUILDDIR)/query/android + $(CXX) $(CXXFLAGS) -c -o $@ $< + +$(TEST_DIR)/test_%: $(BUILDDIR)/catch2/catch.o $(OBJ) $(TEST_DIR)/test_%.cpp + mkdir -p $(TEST_DIR) + $(CXX) $(CXXFLAGS) $(BUILDDIR)/toml++/toml.o $(BUILDDIR)/json/json.o $^ -o $@ $(LDFLAGS) + +bin: $(TESTS) + +clean: + rm -rf $(TESTS) $(BUILDDIR) + make -C .. clean + +distclean: clean + +.PHONY: distclean clean catch2 fmt toml locale bin all diff --git a/tests/catch2/catch_amalgamated.cpp b/tests/catch2/catch_amalgamated.cpp new file mode 100644 index 00000000..b979eb24 --- /dev/null +++ b/tests/catch2/catch_amalgamated.cpp @@ -0,0 +1,11817 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.8.1 +// Generated: 2025-04-08 12:33:19.863332 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- + +#include "catch_amalgamated.hpp" + + +#ifndef CATCH_WINDOWS_H_PROXY_HPP_INCLUDED +#define CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + + +#if defined(CATCH_PLATFORM_WINDOWS) + +// We might end up with the define made globally through the compiler, +// and we don't want to trigger warnings for this +#if !defined(NOMINMAX) +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +#endif + +#include <windows.h> + +#endif // defined(CATCH_PLATFORM_WINDOWS) + +#endif // CATCH_WINDOWS_H_PROXY_HPP_INCLUDED + + + + +namespace Catch { + namespace Benchmark { + namespace Detail { + ChronometerConcept::~ChronometerConcept() = default; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + +// Adapted from donated nonius code. + + +#include <vector> + +namespace Catch { + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last) { + if (!cfg.benchmarkNoAnalysis()) { + std::vector<double> samples; + samples.reserve(static_cast<size_t>(last - first)); + for (auto current = first; current != last; ++current) { + samples.push_back( current->count() ); + } + + auto analysis = Catch::Benchmark::Detail::analyse_samples( + cfg.benchmarkConfidenceInterval(), + cfg.benchmarkResamples(), + samples.data(), + samples.data() + samples.size() ); + auto outliers = Catch::Benchmark::Detail::classify_outliers( + samples.data(), samples.data() + samples.size() ); + + auto wrap_estimate = [](Estimate<double> e) { + return Estimate<FDuration> { + FDuration(e.point), + FDuration(e.lower_bound), + FDuration(e.upper_bound), + e.confidence_interval, + }; + }; + std::vector<FDuration> samples2; + samples2.reserve(samples.size()); + for (auto s : samples) { + samples2.push_back( FDuration( s ) ); + } + + return { + CATCH_MOVE(samples2), + wrap_estimate(analysis.mean), + wrap_estimate(analysis.standard_deviation), + outliers, + analysis.outlier_variance, + }; + } else { + std::vector<FDuration> samples; + samples.reserve(static_cast<size_t>(last - first)); + + FDuration mean = FDuration(0); + int i = 0; + for (auto it = first; it < last; ++it, ++i) { + samples.push_back(*it); + mean += *it; + } + mean /= i; + + return SampleAnalysis{ + CATCH_MOVE(samples), + Estimate<FDuration>{ mean, mean, mean, 0.0 }, + Estimate<FDuration>{ FDuration( 0 ), + FDuration( 0 ), + FDuration( 0 ), + 0.0 }, + OutlierClassification{}, + 0.0 + }; + } + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct do_nothing { + void operator()() const {} + }; + + BenchmarkFunction::callable::~callable() = default; + BenchmarkFunction::BenchmarkFunction(): + f( new model<do_nothing>{ {} } ){} + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + + +#include <exception> + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct optimized_away_error : std::exception { + const char* what() const noexcept override; + }; + + const char* optimized_away_error::what() const noexcept { + return "could not measure benchmark, maybe it was optimized away"; + } + + void throw_optimized_away_error() { + Catch::throw_exception(optimized_away_error{}); + } + + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + +// Adapted from donated nonius code. + + + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstddef> +#include <numeric> +#include <random> + + +#if defined(CATCH_CONFIG_USE_ASYNC) +#include <future> +#endif + +namespace Catch { + namespace Benchmark { + namespace Detail { + namespace { + + template <typename URng, typename Estimator> + static sample + resample( URng& rng, + unsigned int resamples, + double const* first, + double const* last, + Estimator& estimator ) { + auto n = static_cast<size_t>( last - first ); + Catch::uniform_integer_distribution<size_t> dist( 0, n - 1 ); + + sample out; + out.reserve( resamples ); + std::vector<double> resampled; + resampled.reserve( n ); + for ( size_t i = 0; i < resamples; ++i ) { + resampled.clear(); + for ( size_t s = 0; s < n; ++s ) { + resampled.push_back( first[dist( rng )] ); + } + const auto estimate = + estimator( resampled.data(), resampled.data() + resampled.size() ); + out.push_back( estimate ); + } + std::sort( out.begin(), out.end() ); + return out; + } + + static double outlier_variance( Estimate<double> mean, + Estimate<double> stddev, + int n ) { + double sb = stddev.point; + double mn = mean.point / n; + double mg_min = mn / 2.; + double sg = (std::min)( mg_min / 4., sb / std::sqrt( n ) ); + double sg2 = sg * sg; + double sb2 = sb * sb; + + auto c_max = [n, mn, sb2, sg2]( double x ) -> double { + double k = mn - x; + double d = k * k; + double nd = n * d; + double k0 = -n * nd; + double k1 = sb2 - n * sg2 + nd; + double det = k1 * k1 - 4 * sg2 * k0; + return static_cast<int>( -2. * k0 / + ( k1 + std::sqrt( det ) ) ); + }; + + auto var_out = [n, sb2, sg2]( double c ) { + double nc = n - c; + return ( nc / n ) * ( sb2 - nc * sg2 ); + }; + + return (std::min)( var_out( 1 ), + var_out( + (std::min)( c_max( 0. ), + c_max( mg_min ) ) ) ) / + sb2; + } + + static double erf_inv( double x ) { + // Code accompanying the article "Approximating the erfinv + // function" in GPU Computing Gems, Volume 2 + double w, p; + + w = -log( ( 1.0 - x ) * ( 1.0 + x ) ); + + if ( w < 6.250000 ) { + w = w - 3.125000; + p = -3.6444120640178196996e-21; + p = -1.685059138182016589e-19 + p * w; + p = 1.2858480715256400167e-18 + p * w; + p = 1.115787767802518096e-17 + p * w; + p = -1.333171662854620906e-16 + p * w; + p = 2.0972767875968561637e-17 + p * w; + p = 6.6376381343583238325e-15 + p * w; + p = -4.0545662729752068639e-14 + p * w; + p = -8.1519341976054721522e-14 + p * w; + p = 2.6335093153082322977e-12 + p * w; + p = -1.2975133253453532498e-11 + p * w; + p = -5.4154120542946279317e-11 + p * w; + p = 1.051212273321532285e-09 + p * w; + p = -4.1126339803469836976e-09 + p * w; + p = -2.9070369957882005086e-08 + p * w; + p = 4.2347877827932403518e-07 + p * w; + p = -1.3654692000834678645e-06 + p * w; + p = -1.3882523362786468719e-05 + p * w; + p = 0.0001867342080340571352 + p * w; + p = -0.00074070253416626697512 + p * w; + p = -0.0060336708714301490533 + p * w; + p = 0.24015818242558961693 + p * w; + p = 1.6536545626831027356 + p * w; + } else if ( w < 16.000000 ) { + w = sqrt( w ) - 3.250000; + p = 2.2137376921775787049e-09; + p = 9.0756561938885390979e-08 + p * w; + p = -2.7517406297064545428e-07 + p * w; + p = 1.8239629214389227755e-08 + p * w; + p = 1.5027403968909827627e-06 + p * w; + p = -4.013867526981545969e-06 + p * w; + p = 2.9234449089955446044e-06 + p * w; + p = 1.2475304481671778723e-05 + p * w; + p = -4.7318229009055733981e-05 + p * w; + p = 6.8284851459573175448e-05 + p * w; + p = 2.4031110387097893999e-05 + p * w; + p = -0.0003550375203628474796 + p * w; + p = 0.00095328937973738049703 + p * w; + p = -0.0016882755560235047313 + p * w; + p = 0.0024914420961078508066 + p * w; + p = -0.0037512085075692412107 + p * w; + p = 0.005370914553590063617 + p * w; + p = 1.0052589676941592334 + p * w; + p = 3.0838856104922207635 + p * w; + } else { + w = sqrt( w ) - 5.000000; + p = -2.7109920616438573243e-11; + p = -2.5556418169965252055e-10 + p * w; + p = 1.5076572693500548083e-09 + p * w; + p = -3.7894654401267369937e-09 + p * w; + p = 7.6157012080783393804e-09 + p * w; + p = -1.4960026627149240478e-08 + p * w; + p = 2.9147953450901080826e-08 + p * w; + p = -6.7711997758452339498e-08 + p * w; + p = 2.2900482228026654717e-07 + p * w; + p = -9.9298272942317002539e-07 + p * w; + p = 4.5260625972231537039e-06 + p * w; + p = -1.9681778105531670567e-05 + p * w; + p = 7.5995277030017761139e-05 + p * w; + p = -0.00021503011930044477347 + p * w; + p = -0.00013871931833623122026 + p * w; + p = 1.0103004648645343977 + p * w; + p = 4.8499064014085844221 + p * w; + } + return p * x; + } + + static double + standard_deviation( double const* first, double const* last ) { + auto m = Catch::Benchmark::Detail::mean( first, last ); + double variance = + std::accumulate( first, + last, + 0., + [m]( double a, double b ) { + double diff = b - m; + return a + diff * diff; + } ) / + static_cast<double>( last - first ); + return std::sqrt( variance ); + } + + static sample jackknife( double ( *estimator )( double const*, + double const* ), + double* first, + double* last ) { + const auto second = first + 1; + sample results; + results.reserve( static_cast<size_t>( last - first ) ); + + for ( auto it = first; it != last; ++it ) { + std::iter_swap( it, first ); + results.push_back( estimator( second, last ) ); + } + + return results; + } + + + } // namespace + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +namespace Catch { + namespace Benchmark { + namespace Detail { + + double weighted_average_quantile( int k, + int q, + double* first, + double* last ) { + auto count = last - first; + double idx = static_cast<double>((count - 1) * k) / static_cast<double>(q); + int j = static_cast<int>(idx); + double g = idx - j; + std::nth_element(first, first + j, last); + auto xj = first[j]; + if ( Catch::Detail::directCompare( g, 0 ) ) { + return xj; + } + + auto xj1 = *std::min_element(first + (j + 1), last); + return xj + g * (xj1 - xj); + } + + OutlierClassification + classify_outliers( double const* first, double const* last ) { + std::vector<double> copy( first, last ); + + auto q1 = weighted_average_quantile( 1, 4, copy.data(), copy.data() + copy.size() ); + auto q3 = weighted_average_quantile( 3, 4, copy.data(), copy.data() + copy.size() ); + auto iqr = q3 - q1; + auto los = q1 - ( iqr * 3. ); + auto lom = q1 - ( iqr * 1.5 ); + auto him = q3 + ( iqr * 1.5 ); + auto his = q3 + ( iqr * 3. ); + + OutlierClassification o; + for ( ; first != last; ++first ) { + const double t = *first; + if ( t < los ) { + ++o.low_severe; + } else if ( t < lom ) { + ++o.low_mild; + } else if ( t > his ) { + ++o.high_severe; + } else if ( t > him ) { + ++o.high_mild; + } + ++o.samples_seen; + } + return o; + } + + double mean( double const* first, double const* last ) { + auto count = last - first; + double sum = 0.; + while (first != last) { + sum += *first; + ++first; + } + return sum / static_cast<double>(count); + } + + double normal_cdf( double x ) { + return std::erfc( -x / std::sqrt( 2.0 ) ) / 2.0; + } + + double erfc_inv(double x) { + return erf_inv(1.0 - x); + } + + double normal_quantile(double p) { + static const double ROOT_TWO = std::sqrt(2.0); + + double result = 0.0; + assert(p >= 0 && p <= 1); + if (p < 0 || p > 1) { + return result; + } + + result = -erfc_inv(2.0 * p); + // result *= normal distribution standard deviation (1.0) * sqrt(2) + result *= /*sd * */ ROOT_TWO; + // result += normal disttribution mean (0) + return result; + } + + Estimate<double> + bootstrap( double confidence_level, + double* first, + double* last, + sample const& resample, + double ( *estimator )( double const*, double const* ) ) { + auto n_samples = last - first; + + double point = estimator( first, last ); + // Degenerate case with a single sample + if ( n_samples == 1 ) + return { point, point, point, confidence_level }; + + sample jack = jackknife( estimator, first, last ); + double jack_mean = + mean( jack.data(), jack.data() + jack.size() ); + double sum_squares = 0, sum_cubes = 0; + for ( double x : jack ) { + auto difference = jack_mean - x; + auto square = difference * difference; + auto cube = square * difference; + sum_squares += square; + sum_cubes += cube; + } + + double accel = sum_cubes / ( 6 * std::pow( sum_squares, 1.5 ) ); + long n = static_cast<long>( resample.size() ); + double prob_n = static_cast<double>( + std::count_if( resample.begin(), + resample.end(), + [point]( double x ) { return x < point; } )) / + static_cast<double>( n ); + // degenerate case with uniform samples + if ( Catch::Detail::directCompare( prob_n, 0. ) ) { + return { point, point, point, confidence_level }; + } + + double bias = normal_quantile( prob_n ); + double z1 = normal_quantile( ( 1. - confidence_level ) / 2. ); + + auto cumn = [n]( double x ) -> long { + return std::lround( normal_cdf( x ) * + static_cast<double>( n ) ); + }; + auto a = [bias, accel]( double b ) { + return bias + b / ( 1. - accel * b ); + }; + double b1 = bias + z1; + double b2 = bias - z1; + double a1 = a( b1 ); + double a2 = a( b2 ); + auto lo = static_cast<size_t>( (std::max)( cumn( a1 ), 0l ) ); + auto hi = + static_cast<size_t>( (std::min)( cumn( a2 ), n - 1 ) ); + + return { point, resample[lo], resample[hi], confidence_level }; + } + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double* first, + double* last) { + auto mean = &Detail::mean; + auto stddev = &standard_deviation; + +#if defined(CATCH_CONFIG_USE_ASYNC) + auto Estimate = [=](double(*f)(double const*, double const*)) { + std::random_device rd; + auto seed = rd(); + return std::async(std::launch::async, [=] { + SimplePcg32 rng( seed ); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }); + }; + + auto mean_future = Estimate(mean); + auto stddev_future = Estimate(stddev); + + auto mean_estimate = mean_future.get(); + auto stddev_estimate = stddev_future.get(); +#else + auto Estimate = [=](double(*f)(double const* , double const*)) { + std::random_device rd; + auto seed = rd(); + SimplePcg32 rng( seed ); + auto resampled = resample(rng, n_resamples, first, last, f); + return bootstrap(confidence_level, first, last, resampled, f); + }; + + auto mean_estimate = Estimate(mean); + auto stddev_estimate = Estimate(stddev); +#endif // CATCH_USE_ASYNC + + auto n = static_cast<int>(last - first); // seriously, one can't use integral types without hell in C++ + double outlier_variance = Detail::outlier_variance(mean_estimate, stddev_estimate, n); + + return { mean_estimate, stddev_estimate, outlier_variance }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + + + +#include <cmath> +#include <limits> + +namespace { + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +} + +namespace Catch { + + Approx::Approx ( double value ) + : m_epsilon( static_cast<double>(std::numeric_limits<float>::epsilon())*100. ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} + + Approx Approx::custom() { + return Approx( 0 ); + } + + Approx Approx::operator-() const { + auto temp(*this); + temp.m_value = -temp.m_value; + return temp; + } + + + std::string Approx::toString() const { + ReusableStringStream rss; + rss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return rss.str(); + } + + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) + || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value))); + } + + void Approx::setMargin(double newMargin) { + CATCH_ENFORCE(newMargin >= 0, + "Invalid Approx::margin: " << newMargin << '.' + << " Approx::Margin has to be non-negative."); + m_margin = newMargin; + } + + void Approx::setEpsilon(double newEpsilon) { + CATCH_ENFORCE(newEpsilon >= 0 && newEpsilon <= 1.0, + "Invalid Approx::epsilon: " << newEpsilon << '.' + << " Approx::epsilon has to be in [0, 1]"); + m_epsilon = newEpsilon; + } + +namespace literals { + Approx operator ""_a(long double val) { + return Approx(val); + } + Approx operator ""_a(unsigned long long val) { + return Approx(val); + } +} // end namespace literals + +std::string StringMaker<Catch::Approx>::convert(Catch::Approx const& value) { + return value.toString(); +} + +} // end namespace Catch + + + +namespace Catch { + + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const& _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} + + std::string AssertionResultData::reconstructExpression() const { + + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + ReusableStringStream rss; + rss << lazyExpression; + reconstructedExpression = rss.str(); + } + } + return reconstructedExpression; + } + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData&& data ) + : m_info( info ), + m_resultData( CATCH_MOVE(data) ) + {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + // Possibly overallocating by 3 characters should be basically free + std::string expr; expr.reserve(m_info.capturedExpression.size() + 3); + if (isFalseTest(m_info.resultDisposition)) { + expr += "!("; + } + expr += m_info.capturedExpression; + if (isFalseTest(m_info.resultDisposition)) { + expr += ')'; + } + return expr; + } + + std::string AssertionResult::getExpressionInMacro() const { + if ( m_info.macroName.empty() ) { + return static_cast<std::string>( m_info.capturedExpression ); + } + std::string expr; + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + return expr; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } + + StringRef AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + StringRef AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + + + +#include <fstream> + +namespace Catch { + + namespace { + static bool enableBazelEnvSupport() { +#if defined( CATCH_CONFIG_BAZEL_SUPPORT ) + return true; +#else + return Detail::getEnv( "BAZEL_TEST" ) != nullptr; +#endif + } + + struct bazelShardingOptions { + unsigned int shardIndex, shardCount; + std::string shardFilePath; + }; + + static Optional<bazelShardingOptions> readBazelShardingOptions() { + const auto bazelShardIndex = Detail::getEnv( "TEST_SHARD_INDEX" ); + const auto bazelShardTotal = Detail::getEnv( "TEST_TOTAL_SHARDS" ); + const auto bazelShardInfoFile = Detail::getEnv( "TEST_SHARD_STATUS_FILE" ); + + + const bool has_all = + bazelShardIndex && bazelShardTotal && bazelShardInfoFile; + if ( !has_all ) { + // We provide nice warning message if the input is + // misconfigured. + auto warn = []( const char* env_var ) { + Catch::cerr() + << "Warning: Bazel shard configuration is missing '" + << env_var << "'. Shard configuration is skipped.\n"; + }; + if ( !bazelShardIndex ) { + warn( "TEST_SHARD_INDEX" ); + } + if ( !bazelShardTotal ) { + warn( "TEST_TOTAL_SHARDS" ); + } + if ( !bazelShardInfoFile ) { + warn( "TEST_SHARD_STATUS_FILE" ); + } + return {}; + } + + auto shardIndex = parseUInt( bazelShardIndex ); + if ( !shardIndex ) { + Catch::cerr() + << "Warning: could not parse 'TEST_SHARD_INDEX' ('" << bazelShardIndex + << "') as unsigned int.\n"; + return {}; + } + auto shardTotal = parseUInt( bazelShardTotal ); + if ( !shardTotal ) { + Catch::cerr() + << "Warning: could not parse 'TEST_TOTAL_SHARD' ('" + << bazelShardTotal << "') as unsigned int.\n"; + return {}; + } + + return bazelShardingOptions{ + *shardIndex, *shardTotal, bazelShardInfoFile }; + + } + } // end namespace + + + bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return lhs.name == rhs.name && + lhs.outputFilename == rhs.outputFilename && + lhs.colourMode == rhs.colourMode && + lhs.customOptions == rhs.customOptions; + } + + Config::Config( ConfigData const& data ): + m_data( data ) { + // We need to trim filter specs to avoid trouble with superfluous + // whitespace (esp. important for bdd macros, as those are manually + // aligned with whitespace). + + for (auto& elem : m_data.testsOrTags) { + elem = trim(elem); + } + for (auto& elem : m_data.sectionsToRun) { + elem = trim(elem); + } + + // Insert the default reporter if user hasn't asked for a specific one + if ( m_data.reporterSpecifications.empty() ) { +#if defined( CATCH_CONFIG_DEFAULT_REPORTER ) + const auto default_spec = CATCH_CONFIG_DEFAULT_REPORTER; +#else + const auto default_spec = "console"; +#endif + auto parsed = parseReporterSpec(default_spec); + CATCH_ENFORCE( parsed, + "Cannot parse the provided default reporter spec: '" + << default_spec << '\'' ); + m_data.reporterSpecifications.push_back( std::move( *parsed ) ); + } + + if ( enableBazelEnvSupport() ) { + readBazelEnvVars(); + } + + // Bazel support can modify the test specs, so parsing has to happen + // after reading Bazel env vars. + TestSpecParser parser( ITagAliasRegistry::get() ); + if ( !m_data.testsOrTags.empty() ) { + m_hasTestFilters = true; + for ( auto const& testOrTags : m_data.testsOrTags ) { + parser.parse( testOrTags ); + } + } + m_testSpec = parser.testSpec(); + + + // We now fixup the reporter specs to handle default output spec, + // default colour spec, etc + bool defaultOutputUsed = false; + for ( auto const& reporterSpec : m_data.reporterSpecifications ) { + // We do the default-output check separately, while always + // using the default output below to make the code simpler + // and avoid superfluous copies. + if ( reporterSpec.outputFile().none() ) { + CATCH_ENFORCE( !defaultOutputUsed, + "Internal error: cannot use default output for " + "multiple reporters" ); + defaultOutputUsed = true; + } + + m_processedReporterSpecs.push_back( ProcessedReporterSpec{ + reporterSpec.name(), + reporterSpec.outputFile() ? *reporterSpec.outputFile() + : data.defaultOutputFilename, + reporterSpec.colourMode().valueOr( data.defaultColourMode ), + reporterSpec.customOptions() } ); + } + } + + Config::~Config() = default; + + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + bool Config::listListeners() const { return m_data.listListeners; } + + std::vector<std::string> const& Config::getTestsOrTags() const { return m_data.testsOrTags; } + std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + std::vector<ReporterSpec> const& Config::getReporterSpecs() const { + return m_data.reporterSpecifications; + } + + std::vector<ProcessedReporterSpec> const& + Config::getProcessedReporterSpecs() const { + return m_processedReporterSpecs; + } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + bool Config::hasTestFilters() const { return m_hasTestFilters; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + StringRef Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { + return !!( m_data.warnings & WarnAbout::NoAssertions ); + } + bool Config::warnAboutUnmatchedTestSpecs() const { + return !!( m_data.warnings & WarnAbout::UnmatchedTestSpec ); + } + bool Config::zeroTestsCountAsSuccess() const { return m_data.allowZeroTests; } + ShowDurations Config::showDurations() const { return m_data.showDurations; } + double Config::minDuration() const { return m_data.minDuration; } + TestRunOrder Config::runOrder() const { return m_data.runOrder; } + uint32_t Config::rngSeed() const { return m_data.rngSeed; } + unsigned int Config::shardCount() const { return m_data.shardCount; } + unsigned int Config::shardIndex() const { return m_data.shardIndex; } + ColourMode Config::defaultColourMode() const { return m_data.defaultColourMode; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + bool Config::skipBenchmarks() const { return m_data.skipBenchmarks; } + bool Config::benchmarkNoAnalysis() const { return m_data.benchmarkNoAnalysis; } + unsigned int Config::benchmarkSamples() const { return m_data.benchmarkSamples; } + double Config::benchmarkConfidenceInterval() const { return m_data.benchmarkConfidenceInterval; } + unsigned int Config::benchmarkResamples() const { return m_data.benchmarkResamples; } + std::chrono::milliseconds Config::benchmarkWarmupTime() const { return std::chrono::milliseconds(m_data.benchmarkWarmupTime); } + + void Config::readBazelEnvVars() { + // Register a JUnit reporter for Bazel. Bazel sets an environment + // variable with the path to XML output. If this file is written to + // during test, Bazel will not generate a default XML output. + // This allows the XML output file to contain higher level of detail + // than what is possible otherwise. + const auto bazelOutputFile = Detail::getEnv( "XML_OUTPUT_FILE" ); + + if ( bazelOutputFile ) { + m_data.reporterSpecifications.push_back( + { "junit", std::string( bazelOutputFile ), {}, {} } ); + } + + const auto bazelTestSpec = Detail::getEnv( "TESTBRIDGE_TEST_ONLY" ); + if ( bazelTestSpec ) { + // Presumably the test spec from environment should overwrite + // the one we got from CLI (if we got any) + m_data.testsOrTags.clear(); + m_data.testsOrTags.push_back( bazelTestSpec ); + } + + const auto bazelShardOptions = readBazelShardingOptions(); + if ( bazelShardOptions ) { + std::ofstream f( bazelShardOptions->shardFilePath, + std::ios_base::out | std::ios_base::trunc ); + if ( f.is_open() ) { + f << ""; + m_data.shardIndex = bazelShardOptions->shardIndex; + m_data.shardCount = bazelShardOptions->shardCount; + } + } + } + +} // end namespace Catch + + + + + +namespace Catch { + std::uint32_t getSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } +} + + + +#include <cassert> +#include <stack> + +namespace Catch { + + //////////////////////////////////////////////////////////////////////////// + + + ScopedMessage::ScopedMessage( MessageBuilder&& builder ): + m_info( CATCH_MOVE(builder.m_info) ) { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::ScopedMessage( ScopedMessage&& old ) noexcept: + m_info( CATCH_MOVE( old.m_info ) ) { + old.m_moved = true; + } + + ScopedMessage::~ScopedMessage() { + if ( !uncaught_exceptions() && !m_moved ){ + getResultCapture().popScopedMessage(m_info); + } + } + + + Capturer::Capturer( StringRef macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType resultType, + StringRef names ): + m_resultCapture( getResultCapture() ) { + auto trimmed = [&] (size_t start, size_t end) { + while (names[start] == ',' || isspace(static_cast<unsigned char>(names[start]))) { + ++start; + } + while (names[end] == ',' || isspace(static_cast<unsigned char>(names[end]))) { + --end; + } + return names.substr(start, end - start + 1); + }; + auto skipq = [&] (size_t start, char quote) { + for (auto i = start + 1; i < names.size() ; ++i) { + if (names[i] == quote) + return i; + if (names[i] == '\\') + ++i; + } + CATCH_INTERNAL_ERROR("CAPTURE parsing encountered unmatched quote"); + }; + + size_t start = 0; + std::stack<char> openings; + for (size_t pos = 0; pos < names.size(); ++pos) { + char c = names[pos]; + switch (c) { + case '[': + case '{': + case '(': + // It is basically impossible to disambiguate between + // comparison and start of template args in this context +// case '<': + openings.push(c); + break; + case ']': + case '}': + case ')': +// case '>': + openings.pop(); + break; + case '"': + case '\'': + pos = skipq(pos, c); + break; + case ',': + if (start != pos && openings.empty()) { + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast<std::string>(trimmed(start, pos)); + m_messages.back().message += " := "; + start = pos; + } + break; + default:; // noop + } + } + assert(openings.empty() && "Mismatched openings"); + m_messages.emplace_back(macroName, lineInfo, resultType); + m_messages.back().message = static_cast<std::string>(trimmed(start, names.size() - 1)); + m_messages.back().message += " := "; + } + Capturer::~Capturer() { + if ( !uncaught_exceptions() ){ + assert( m_captured == m_messages.size() ); + for( size_t i = 0; i < m_captured; ++i ) + m_resultCapture.popScopedMessage( m_messages[i] ); + } + } + + void Capturer::captureValue( size_t index, std::string const& value ) { + assert( index < m_messages.size() ); + m_messages[index].message += value; + m_resultCapture.pushScopedMessage( m_messages[index] ); + m_captured++; + } + +} // end namespace Catch + + + + +#include <exception> + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, + public IMutableRegistryHub, + private Detail::NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + ReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr factory ) override { + m_reporterRegistry.registerReporter( name, CATCH_MOVE(factory) ); + } + void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) override { + m_reporterRegistry.registerListener( CATCH_MOVE(factory) ); + } + void registerTest( Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker ) override { + m_testCaseRegistry.registerTest( CATCH_MOVE(testInfo), CATCH_MOVE(invoker) ); + } + void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( CATCH_MOVE(translator) ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + m_exceptionRegistry.add(std::current_exception()); +#else + CATCH_INTERNAL_ERROR("Attempted to register active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); +#endif + } + IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() override { + return m_enumValuesRegistry; + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + Detail::EnumValuesRegistry m_enumValuesRegistry; + }; + } + + using RegistryHubSingleton = Singleton<RegistryHub, IRegistryHub, IMutableRegistryHub>; + + IRegistryHub const& getRegistryHub() { + return RegistryHubSingleton::get(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return RegistryHubSingleton::getMutable(); + } + void cleanUp() { + cleanupSingletons(); + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + + +} // end namespace Catch + + + +#include <algorithm> +#include <cassert> +#include <exception> +#include <iomanip> +#include <set> + +namespace Catch { + + namespace { + static constexpr int TestFailureExitCode = 42; + static constexpr int UnspecifiedErrorExitCode = 1; + static constexpr int AllTestsSkippedExitCode = 4; + static constexpr int NoTestsRunExitCode = 2; + static constexpr int UnmatchedTestSpecExitCode = 3; + static constexpr int InvalidTestSpecExitCode = 5; + + + IEventListenerPtr createReporter(std::string const& reporterName, ReporterConfig&& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, CATCH_MOVE(config)); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << '\''); + + return reporter; + } + + IEventListenerPtr prepareReporters(Config const* config) { + if (Catch::getRegistryHub().getReporterRegistry().getListeners().empty() + && config->getProcessedReporterSpecs().size() == 1) { + auto const& spec = config->getProcessedReporterSpecs()[0]; + return createReporter( + spec.name, + ReporterConfig( config, + makeStream( spec.outputFilename ), + spec.colourMode, + spec.customOptions ) ); + } + + auto multi = Detail::make_unique<MultiReporter>(config); + + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) { + multi->addListener(listener->create(config)); + } + + for ( auto const& reporterSpec : config->getProcessedReporterSpecs() ) { + multi->addReporter( createReporter( + reporterSpec.name, + ReporterConfig( config, + makeStream( reporterSpec.outputFilename ), + reporterSpec.colourMode, + reporterSpec.customOptions ) ) ); + } + + return multi; + } + + class TestGroup { + public: + explicit TestGroup(IEventListenerPtr&& reporter, Config const* config): + m_reporter(reporter.get()), + m_config{config}, + m_context{config, CATCH_MOVE(reporter)} { + + assert( m_config->testSpec().getInvalidSpecs().empty() && + "Invalid test specs should be handled before running tests" ); + + auto const& allTestCases = getAllTestCasesSorted(*m_config); + auto const& testSpec = m_config->testSpec(); + if ( !testSpec.hasFilters() ) { + for ( auto const& test : allTestCases ) { + if ( !test.getTestCaseInfo().isHidden() ) { + m_tests.emplace( &test ); + } + } + } else { + m_matches = + testSpec.matchesByFilter( allTestCases, *m_config ); + for ( auto const& match : m_matches ) { + m_tests.insert( match.tests.begin(), + match.tests.end() ); + } + } + + m_tests = createShard(m_tests, m_config->shardCount(), m_config->shardIndex()); + } + + Totals execute() { + Totals totals; + for (auto const& testCase : m_tests) { + if (!m_context.aborting()) + totals += m_context.runTest(*testCase); + else + m_reporter->skipTest(testCase->getTestCaseInfo()); + } + + for (auto const& match : m_matches) { + if (match.tests.empty()) { + m_unmatchedTestSpecs = true; + m_reporter->noMatchingTestCases( match.name ); + } + } + + return totals; + } + + bool hadUnmatchedTestSpecs() const { + return m_unmatchedTestSpecs; + } + + + private: + IEventListener* m_reporter; + Config const* m_config; + RunContext m_context; + std::set<TestCaseHandle const*> m_tests; + TestSpec::Matches m_matches; + bool m_unmatchedTestSpecs = false; + }; + + void applyFilenamesAsTags() { + for (auto const& testInfo : getRegistryHub().getTestCaseRegistry().getAllInfos()) { + testInfo->addFilenameTag(); + } + } + + } // anon namespace + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + CATCH_TRY { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + CATCH_CATCH_ALL { getMutableRegistryHub().registerStartupException(); } + } + + // There cannot be exceptions at startup in no-exception mode. +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + + m_startupExceptions = true; + auto errStream = makeStream( "%stderr" ); + auto colourImpl = makeColourImpl( + ColourMode::PlatformDefault, errStream.get() ); + auto guard = colourImpl->guardColour( Colour::Red ); + errStream->stream() << "Errors occurred during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + errStream->stream() << TextFlow::Column( ex.what() ).indent(2) << '\n'; + } + } + } +#endif + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch2 v" << libraryVersion() << '\n' + << m_cli << '\n' + << "For more detailed usage please see the project docs\n\n" << std::flush; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch2 test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch2\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << '\n' << std::flush; + } + + int Session::applyCommandLine( int argc, char const * const * argv ) { + if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } + + auto result = m_cli.parse( Clara::Args( argc, argv ) ); + + if( !result ) { + config(); + getCurrentMutableContext().setConfig(m_config.get()); + auto errStream = makeStream( "%stderr" ); + auto colour = makeColourImpl( ColourMode::PlatformDefault, errStream.get() ); + + errStream->stream() + << colour->guardColour( Colour::Red ) + << "\nError(s) in input:\n" + << TextFlow::Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + errStream->stream() << "Run with -? for usage\n\n" << std::flush; + return UnspecifiedErrorExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + + m_config.reset(); + return 0; + } + +#if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int Session::applyCommandLine( int argc, wchar_t const * const * argv ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, nullptr, 0, nullptr, nullptr ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, nullptr, nullptr ); + } + + int returnCode = applyCommandLine( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting\n" << std::flush; + static_cast<void>(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << '\n' << std::flush; + static_cast<void>(std::getchar()); + } + return exitCode; + } + + Clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( Clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = Detail::make_unique<Config>( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if ( m_startupExceptions ) { return UnspecifiedErrorExitCode; } + + if (m_configData.showHelp || m_configData.libIdentify) { + return 0; + } + + if ( m_configData.shardIndex >= m_configData.shardCount ) { + Catch::cerr() << "The shard count (" << m_configData.shardCount + << ") must be greater than the shard index (" + << m_configData.shardIndex << ")\n" + << std::flush; + return UnspecifiedErrorExitCode; + } + + CATCH_TRY { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if (m_configData.filenamesAsTags) { + applyFilenamesAsTags(); + } + + // Set up global config instance before we start calling into other functions + getCurrentMutableContext().setConfig(m_config.get()); + + // Create reporter(s) so we can route listings through them + auto reporter = prepareReporters(m_config.get()); + + auto const& invalidSpecs = m_config->testSpec().getInvalidSpecs(); + if ( !invalidSpecs.empty() ) { + for ( auto const& spec : invalidSpecs ) { + reporter->reportInvalidTestSpec( spec ); + } + return InvalidTestSpecExitCode; + } + + + // Handle list request + if (list(*reporter, *m_config)) { + return 0; + } + + TestGroup tests { CATCH_MOVE(reporter), m_config.get() }; + auto const totals = tests.execute(); + + if ( tests.hadUnmatchedTestSpecs() + && m_config->warnAboutUnmatchedTestSpecs() ) { + // UnmatchedTestSpecExitCode + return UnmatchedTestSpecExitCode; + } + + if ( totals.testCases.total() == 0 + && !m_config->zeroTestsCountAsSuccess() ) { + return NoTestsRunExitCode; + } + + if ( totals.testCases.total() > 0 && + totals.testCases.total() == totals.testCases.skipped + && !m_config->zeroTestsCountAsSuccess() ) { + return AllTestsSkippedExitCode; + } + + if ( totals.assertions.failed ) { return TestFailureExitCode; } + return 0; + + } +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << '\n' << std::flush; + return UnspecifiedErrorExitCode; + } +#endif + } + +} // end namespace Catch + + + + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + CATCH_TRY { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} + + + +#include <cassert> +#include <cctype> +#include <algorithm> + +namespace Catch { + + namespace { + using TCP_underlying_type = uint8_t; + static_assert(sizeof(TestCaseProperties) == sizeof(TCP_underlying_type), + "The size of the TestCaseProperties is different from the assumed size"); + + constexpr TestCaseProperties operator|(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast<TestCaseProperties>( + static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs) + ); + } + + constexpr TestCaseProperties& operator|=(TestCaseProperties& lhs, TestCaseProperties rhs) { + lhs = static_cast<TestCaseProperties>( + static_cast<TCP_underlying_type>(lhs) | static_cast<TCP_underlying_type>(rhs) + ); + return lhs; + } + + constexpr TestCaseProperties operator&(TestCaseProperties lhs, TestCaseProperties rhs) { + return static_cast<TestCaseProperties>( + static_cast<TCP_underlying_type>(lhs) & static_cast<TCP_underlying_type>(rhs) + ); + } + + constexpr bool applies(TestCaseProperties tcp) { + static_assert(static_cast<TCP_underlying_type>(TestCaseProperties::None) == 0, + "TestCaseProperties::None must be equal to 0"); + return tcp != TestCaseProperties::None; + } + + TestCaseProperties parseSpecialTag( StringRef tag ) { + if( !tag.empty() && tag[0] == '.' ) + return TestCaseProperties::IsHidden; + else if( tag == "!throws"_sr ) + return TestCaseProperties::Throws; + else if( tag == "!shouldfail"_sr ) + return TestCaseProperties::ShouldFail; + else if( tag == "!mayfail"_sr ) + return TestCaseProperties::MayFail; + else if( tag == "!nonportable"_sr ) + return TestCaseProperties::NonPortable; + else if( tag == "!benchmark"_sr ) + return TestCaseProperties::Benchmark | TestCaseProperties::IsHidden; + else + return TestCaseProperties::None; + } + bool isReservedTag( StringRef tag ) { + return parseSpecialTag( tag ) == TestCaseProperties::None + && tag.size() > 0 + && !std::isalnum( static_cast<unsigned char>(tag[0]) ); + } + void enforceNotReservedTag( StringRef tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alphanumeric characters are reserved\n" + << _lineInfo ); + } + + std::string makeDefaultName() { + static size_t counter = 0; + return "Anonymous test case " + std::to_string(++counter); + } + + constexpr StringRef extractFilenamePart(StringRef filename) { + size_t lastDot = filename.size(); + while (lastDot > 0 && filename[lastDot - 1] != '.') { + --lastDot; + } + // In theory we could have filename without any extension in it + if ( lastDot == 0 ) { return StringRef(); } + + --lastDot; + size_t nameStart = lastDot; + while (nameStart > 0 && filename[nameStart - 1] != '/' && filename[nameStart - 1] != '\\') { + --nameStart; + } + + return filename.substr(nameStart, lastDot - nameStart); + } + + // Returns the upper bound on size of extra tags ([#file]+[.]) + constexpr size_t sizeOfExtraTags(StringRef filepath) { + // [.] is 3, [#] is another 3 + const size_t extras = 3 + 3; + return extractFilenamePart(filepath).size() + extras; + } + } // end unnamed namespace + + bool operator<( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveLess cmp; + return cmp( lhs.original, rhs.original ); + } + bool operator==( Tag const& lhs, Tag const& rhs ) { + Detail::CaseInsensitiveEqualTo cmp; + return cmp( lhs.original, rhs.original ); + } + + Detail::unique_ptr<TestCaseInfo> + makeTestCaseInfo(StringRef _className, + NameAndTags const& nameAndTags, + SourceLineInfo const& _lineInfo ) { + return Detail::make_unique<TestCaseInfo>(_className, nameAndTags, _lineInfo); + } + + TestCaseInfo::TestCaseInfo(StringRef _className, + NameAndTags const& _nameAndTags, + SourceLineInfo const& _lineInfo): + name( _nameAndTags.name.empty() ? makeDefaultName() : _nameAndTags.name ), + className( _className ), + lineInfo( _lineInfo ) + { + StringRef originalTags = _nameAndTags.tags; + // We need to reserve enough space to store all of the tags + // (including optional hidden tag and filename tag) + auto requiredSize = originalTags.size() + sizeOfExtraTags(_lineInfo.file); + backingTags.reserve(requiredSize); + + // We cannot copy the tags directly, as we need to normalize + // some tags, so that [.foo] is copied as [.][foo]. + size_t tagStart = 0; + size_t tagEnd = 0; + bool inTag = false; + for (size_t idx = 0; idx < originalTags.size(); ++idx) { + auto c = originalTags[idx]; + if (c == '[') { + CATCH_ENFORCE( + !inTag, + "Found '[' inside a tag while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo ); + + inTag = true; + tagStart = idx; + } + if (c == ']') { + CATCH_ENFORCE( + inTag, + "Found unmatched ']' while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo ); + + inTag = false; + tagEnd = idx; + assert(tagStart < tagEnd); + + // We need to check the tag for special meanings, copy + // it over to backing storage and actually reference the + // backing storage in the saved tags + StringRef tagStr = originalTags.substr(tagStart+1, tagEnd - tagStart - 1); + CATCH_ENFORCE( !tagStr.empty(), + "Found an empty tag while registering test case '" + << _nameAndTags.name << "' at " + << _lineInfo ); + + enforceNotReservedTag(tagStr, lineInfo); + properties |= parseSpecialTag(tagStr); + // When copying a tag to the backing storage, we need to + // check if it is a merged hide tag, such as [.foo], and + // if it is, we need to handle it as if it was [foo]. + if (tagStr.size() > 1 && tagStr[0] == '.') { + tagStr = tagStr.substr(1, tagStr.size() - 1); + } + // We skip over dealing with the [.] tag, as we will add + // it later unconditionally and then sort and unique all + // the tags. + internalAppendTag(tagStr); + } + } + CATCH_ENFORCE( !inTag, + "Found an unclosed tag while registering test case '" + << _nameAndTags.name << "' at " << _lineInfo ); + + + // Add [.] if relevant + if (isHidden()) { + internalAppendTag("."_sr); + } + + // Sort and prepare tags + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), + end(tags)); + } + + bool TestCaseInfo::isHidden() const { + return applies( properties & TestCaseProperties::IsHidden ); + } + bool TestCaseInfo::throws() const { + return applies( properties & TestCaseProperties::Throws ); + } + bool TestCaseInfo::okToFail() const { + return applies( properties & (TestCaseProperties::ShouldFail | TestCaseProperties::MayFail ) ); + } + bool TestCaseInfo::expectedToFail() const { + return applies( properties & (TestCaseProperties::ShouldFail) ); + } + + void TestCaseInfo::addFilenameTag() { + std::string combined("#"); + combined += extractFilenamePart(lineInfo.file); + internalAppendTag(combined); + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.original.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret += tag.original; + ret.push_back(']'); + } + + return ret; + } + + void TestCaseInfo::internalAppendTag(StringRef tagStr) { + backingTags += '['; + const auto backingStart = backingTags.size(); + backingTags += tagStr; + const auto backingEnd = backingTags.size(); + backingTags += ']'; + tags.emplace_back(StringRef(backingTags.c_str() + backingStart, backingEnd - backingStart)); + } + + bool operator<( TestCaseInfo const& lhs, TestCaseInfo const& rhs ) { + // We want to avoid redoing the string comparisons multiple times, + // so we store the result of a three-way comparison before using + // it in the actual comparison logic. + const auto cmpName = lhs.name.compare( rhs.name ); + if ( cmpName != 0 ) { + return cmpName < 0; + } + const auto cmpClassName = lhs.className.compare( rhs.className ); + if ( cmpClassName != 0 ) { + return cmpClassName < 0; + } + return lhs.tags < rhs.tags; + } + +} // end namespace Catch + + + +#include <algorithm> +#include <string> +#include <vector> +#include <ostream> + +namespace Catch { + + TestSpec::Pattern::Pattern( std::string const& name ) + : m_name( name ) + {} + + TestSpec::Pattern::~Pattern() = default; + + std::string const& TestSpec::Pattern::name() const { + return m_name; + } + + + TestSpec::NamePattern::NamePattern( std::string const& name, std::string const& filterString ) + : Pattern( filterString ) + , m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( testCase.name ); + } + + void TestSpec::NamePattern::serializeTo( std::ostream& out ) const { + out << '"' << name() << '"'; + } + + + TestSpec::TagPattern::TagPattern( std::string const& tag, std::string const& filterString ) + : Pattern( filterString ) + , m_tag( tag ) + {} + + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find( begin( testCase.tags ), + end( testCase.tags ), + Tag( m_tag ) ) != end( testCase.tags ); + } + + void TestSpec::TagPattern::serializeTo( std::ostream& out ) const { + out << name(); + } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + bool should_use = !testCase.isHidden(); + for (auto const& pattern : m_required) { + should_use = true; + if (!pattern->matches(testCase)) { + return false; + } + } + for (auto const& pattern : m_forbidden) { + if (pattern->matches(testCase)) { + return false; + } + } + return should_use; + } + + void TestSpec::Filter::serializeTo( std::ostream& out ) const { + bool first = true; + for ( auto const& pattern : m_required ) { + if ( !first ) { + out << ' '; + } + out << *pattern; + first = false; + } + for ( auto const& pattern : m_forbidden ) { + if ( !first ) { + out << ' '; + } + out << *pattern; + first = false; + } + } + + + std::string TestSpec::extractFilterName( Filter const& filter ) { + Catch::ReusableStringStream sstr; + sstr << filter; + return sstr.str(); + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + return std::any_of( m_filters.begin(), m_filters.end(), [&]( Filter const& f ){ return f.matches( testCase ); } ); + } + + TestSpec::Matches TestSpec::matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const { + Matches matches; + matches.reserve( m_filters.size() ); + for ( auto const& filter : m_filters ) { + std::vector<TestCaseHandle const*> currentMatches; + for ( auto const& test : testCases ) + if ( isThrowSafe( test, config ) && + filter.matches( test.getTestCaseInfo() ) ) + currentMatches.emplace_back( &test ); + matches.push_back( + FilterMatch{ extractFilterName( filter ), currentMatches } ); + } + return matches; + } + + const TestSpec::vectorStrings& TestSpec::getInvalidSpecs() const { + return m_invalidSpecs; + } + + void TestSpec::serializeTo( std::ostream& out ) const { + bool first = true; + for ( auto const& filter : m_filters ) { + if ( !first ) { + out << ','; + } + out << filter; + first = false; + } + } + +} + + + +#include <chrono> + +namespace Catch { + + namespace { + static auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::steady_clock::now().time_since_epoch()).count(); + } + } // end unnamed namespace + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> uint64_t { + return getCurrentNanosecondsSinceEpoch() - m_nanoseconds; + } + auto Timer::getElapsedMicroseconds() const -> uint64_t { + return getElapsedNanoseconds()/1000; + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return static_cast<double>(getElapsedMicroseconds())/1000000.0; + } + + +} // namespace Catch + + + + +#include <cmath> +#include <iomanip> + +namespace Catch { + +namespace Detail { + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch : uint8_t { + Big, + Little + }; + + static Arch which() { + int one = 1; + // If the lowest byte we read is non-zero, we can assume + // that little endian format is used. + auto value = *reinterpret_cast<char*>(&one); + return value ? Little : Big; + } + }; + + template<typename T> + std::string fpToString(T value, int precision) { + if (Catch::isnan(value)) { + return "nan"; + } + + ReusableStringStream rss; + rss << std::setprecision(precision) + << std::fixed + << value; + std::string d = rss.str(); + std::size_t i = d.find_last_not_of('0'); + if (i != std::string::npos && i != d.size() - 1) { + if (d[i] == '.') + i++; + d = d.substr(0, i + 1); + } + return d; + } + } // end unnamed namespace + + std::string convertIntoString(StringRef string, bool escapeInvisibles) { + std::string ret; + // This is enough for the "don't escape invisibles" case, and a good + // lower bound on the "escape invisibles" case. + ret.reserve(string.size() + 2); + + if (!escapeInvisibles) { + ret += '"'; + ret += string; + ret += '"'; + return ret; + } + + ret += '"'; + for (char c : string) { + switch (c) { + case '\r': + ret.append("\\r"); + break; + case '\n': + ret.append("\\n"); + break; + case '\t': + ret.append("\\t"); + break; + case '\f': + ret.append("\\f"); + break; + default: + ret.push_back(c); + break; + } + } + ret += '"'; + + return ret; + } + + std::string convertIntoString(StringRef string) { + return convertIntoString(string, getCurrentContext().getConfig()->showInvisibles()); + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast<unsigned char const *>(object); + ReusableStringStream rss; + rss << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + rss << std::setw(2) << static_cast<unsigned>(bytes[i]); + return rss.str(); + } +} // end Detail namespace + + + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker<std::string>::convert(const std::string& str) { + return Detail::convertIntoString( str ); +} + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::string_view>::convert(std::string_view str) { + return Detail::convertIntoString( StringRef( str.data(), str.size() ) ); +} +#endif + +std::string StringMaker<char const*>::convert(char const* str) { + if (str) { + return Detail::convertIntoString( str ); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<char*>::convert(char* str) { // NOLINT(readability-non-const-parameter) + if (str) { + return Detail::convertIntoString( str ); + } else { + return{ "{null string}" }; + } +} + +#ifdef CATCH_CONFIG_WCHAR +std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast<char>(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW +std::string StringMaker<std::wstring_view>::convert(std::wstring_view str) { + return StringMaker<std::wstring>::convert(std::wstring(str)); +} +# endif + +std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t *>::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +#endif + +#if defined(CATCH_CONFIG_CPP17_BYTE) +#include <cstddef> +std::string StringMaker<std::byte>::convert(std::byte value) { + return ::Catch::Detail::stringify(std::to_integer<unsigned long long>(value)); +} +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + +std::string StringMaker<int>::convert(int value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long>::convert(long value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long long>::convert(long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<unsigned int>::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long>::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long long>::convert(unsigned long long value) { + ReusableStringStream rss; + rss << value; + if (value > Detail::hexThreshold) { + rss << " (0x" << std::hex << value << ')'; + } + return rss.str(); +} + +std::string StringMaker<signed char>::convert(signed char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker<char>::convert(char c) { + return ::Catch::Detail::stringify(static_cast<signed char>(c)); +} +std::string StringMaker<unsigned char>::convert(unsigned char value) { + return ::Catch::Detail::stringify(static_cast<char>(value)); +} + +int StringMaker<float>::precision = std::numeric_limits<float>::max_digits10; + +std::string StringMaker<float>::convert(float value) { + return Detail::fpToString(value, precision) + 'f'; +} + +int StringMaker<double>::precision = std::numeric_limits<double>::max_digits10; + +std::string StringMaker<double>::convert(double value) { + return Detail::fpToString(value, precision); +} + +} // end namespace Catch + + + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + diff.skipped = skipped - other.skipped; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + skipped += other.skipped; + return *this; + } + + std::uint64_t Counts::total() const { + return passed + failed + failedButOk + skipped; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0 && skipped == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else if ( diff.assertions.skipped > 0 ) + ++ diff.testCases.skipped; + else + ++diff.testCases.passed; + return diff; + } + +} + + + + +namespace Catch { + namespace Detail { + void registerTranslatorImpl( + Detail::unique_ptr<IExceptionTranslator>&& translator ) { + getMutableRegistryHub().registerTranslator( + CATCH_MOVE( translator ) ); + } + } // namespace Detail +} // namespace Catch + + +#include <ostream> + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 3, 8, 1, "", 0 ); + return version; + } + +} + + + + +namespace Catch { + + const char* GeneratorException::what() const noexcept { + return m_msg; + } + +} // end namespace Catch + + + + +namespace Catch { + + IGeneratorTracker::~IGeneratorTracker() = default; + +namespace Generators { + +namespace Detail { + + [[noreturn]] + void throw_generator_exception(char const* msg) { + Catch::throw_exception(GeneratorException{ msg }); + } +} // end namespace Detail + + GeneratorUntypedBase::~GeneratorUntypedBase() = default; + + IGeneratorTracker* acquireGeneratorTracker(StringRef generatorName, SourceLineInfo const& lineInfo ) { + return getResultCapture().acquireGeneratorTracker( generatorName, lineInfo ); + } + + IGeneratorTracker* createGeneratorTracker( StringRef generatorName, + SourceLineInfo lineInfo, + GeneratorBasePtr&& generator ) { + return getResultCapture().createGeneratorTracker( + generatorName, lineInfo, CATCH_MOVE( generator ) ); + } + +} // namespace Generators +} // namespace Catch + + + + +#include <random> + +namespace Catch { + namespace Generators { + namespace Detail { + std::uint32_t getSeed() { return sharedRng()(); } + } // namespace Detail + + struct RandomFloatingGenerator<long double>::PImpl { + PImpl( long double a, long double b, uint32_t seed ): + rng( seed ), dist( a, b ) {} + + Catch::SimplePcg32 rng; + std::uniform_real_distribution<long double> dist; + }; + + RandomFloatingGenerator<long double>::RandomFloatingGenerator( + long double a, long double b, std::uint32_t seed) : + m_pimpl(Catch::Detail::make_unique<PImpl>(a, b, seed)) { + static_cast<void>( next() ); + } + + RandomFloatingGenerator<long double>::~RandomFloatingGenerator() = + default; + bool RandomFloatingGenerator<long double>::next() { + m_current_number = m_pimpl->dist( m_pimpl->rng ); + return true; + } + } // namespace Generators +} // namespace Catch + + + + +namespace Catch { + IResultCapture::~IResultCapture() = default; +} + + + + +namespace Catch { + IConfig::~IConfig() = default; +} + + + + +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} + + + +#include <string> + +namespace Catch { + namespace Generators { + + bool GeneratorUntypedBase::countedNext() { + auto ret = next(); + if ( ret ) { + m_stringReprCache.clear(); + ++m_currentElementIndex; + } + return ret; + } + + StringRef GeneratorUntypedBase::currentElementAsString() const { + if ( m_stringReprCache.empty() ) { + m_stringReprCache = stringifyImpl(); + } + return m_stringReprCache; + } + + } // namespace Generators +} // namespace Catch + + + + +namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} + + + +#include <cassert> + +namespace Catch { + + ReporterConfig::ReporterConfig( + IConfig const* _fullConfig, + Detail::unique_ptr<IStream> _stream, + ColourMode colourMode, + std::map<std::string, std::string> customOptions ): + m_stream( CATCH_MOVE(_stream) ), + m_fullConfig( _fullConfig ), + m_colourMode( colourMode ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} + + Detail::unique_ptr<IStream> ReporterConfig::takeStream() && { + assert( m_stream ); + return CATCH_MOVE( m_stream ); + } + IConfig const * ReporterConfig::fullConfig() const { return m_fullConfig; } + ColourMode ReporterConfig::colourMode() const { return m_colourMode; } + + std::map<std::string, std::string> const& + ReporterConfig::customOptions() const { + return m_customOptions; + } + + ReporterConfig::~ReporterConfig() = default; + + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder.m_info.message = static_cast<std::string>(assertionResult.getMessage()); + + infoMessages.push_back( CATCH_MOVE(builder.m_info) ); + } + } + + SectionStats::SectionStats( SectionInfo&& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( CATCH_MOVE(_sectionInfo) ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string&& _stdOut, + std::string&& _stdErr, + bool _aborting ) + : testInfo( &_testInfo ), + totals( _totals ), + stdOut( CATCH_MOVE(_stdOut) ), + stdErr( CATCH_MOVE(_stdErr) ), + aborting( _aborting ) + {} + + + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + + IEventListener::~IEventListener() = default; + +} // end namespace Catch + + + + +namespace Catch { + IReporterFactory::~IReporterFactory() = default; + EventListenerFactory::~EventListenerFactory() = default; +} + + + + +namespace Catch { + ITestCaseRegistry::~ITestCaseRegistry() = default; +} + + + +namespace Catch { + + AssertionHandler::AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition }, + m_resultCapture( getResultCapture() ) + { + m_resultCapture.notifyAssertionStarted( m_assertionInfo ); + } + + void AssertionHandler::handleExpr( ITransientExpression const& expr ) { + m_resultCapture.handleExpr( m_assertionInfo, expr, m_reaction ); + } + void AssertionHandler::handleMessage(ResultWas::OfType resultType, std::string&& message) { + m_resultCapture.handleMessage( m_assertionInfo, resultType, CATCH_MOVE(message), m_reaction ); + } + + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } + + void AssertionHandler::complete() { + m_completed = true; + if( m_reaction.shouldDebugBreak ) { + + // If you find your debugger stopping you here then go one level up on the + // call-stack for the code that caused it (typically a failed assertion) + + // (To go back to the test and change execution, jump over the throw, next) + CATCH_BREAK_INTO_DEBUGGER(); + } + if (m_reaction.shouldThrow) { + throw_test_failure_exception(); + } + if ( m_reaction.shouldSkip ) { + throw_test_skip_exception(); + } + } + + void AssertionHandler::handleUnexpectedInflightException() { + m_resultCapture.handleUnexpectedInflightException( m_assertionInfo, Catch::translateActiveException(), m_reaction ); + } + + void AssertionHandler::handleExceptionThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + void AssertionHandler::handleExceptionNotThrownAsExpected() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + void AssertionHandler::handleUnexpectedExceptionNotThrown() { + m_resultCapture.handleUnexpectedExceptionNotThrown( m_assertionInfo, m_reaction ); + } + + void AssertionHandler::handleThrowingCallSkipped() { + m_resultCapture.handleNonExpr(m_assertionInfo, ResultWas::Ok, m_reaction); + } + + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ) ); + } + +} // namespace Catch + + + + +#include <algorithm> + +namespace Catch { + namespace Detail { + + bool CaseInsensitiveLess::operator()( StringRef lhs, + StringRef rhs ) const { + return std::lexicographical_compare( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) < toLower( r ); } ); + } + + bool + CaseInsensitiveEqualTo::operator()( StringRef lhs, + StringRef rhs ) const { + return std::equal( + lhs.begin(), lhs.end(), + rhs.begin(), rhs.end(), + []( char l, char r ) { return toLower( l ) == toLower( r ); } ); + } + + } // namespace Detail +} // namespace Catch + + + + +#include <algorithm> +#include <ostream> + +namespace { + bool isOptPrefix( char c ) { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + Catch::StringRef normaliseOpt( Catch::StringRef optName ) { + if ( optName[0] == '-' +#if defined(CATCH_PLATFORM_WINDOWS) + || optName[0] == '/' +#endif + ) { + return optName.substr( 1, optName.size() ); + } + + return optName; + } + + static size_t find_first_separator(Catch::StringRef sr) { + auto is_separator = []( char c ) { + return c == ' ' || c == ':' || c == '='; + }; + size_t pos = 0; + while (pos < sr.size()) { + if (is_separator(sr[pos])) { return pos; } + ++pos; + } + + return Catch::StringRef::npos; + } + +} // namespace + +namespace Catch { + namespace Clara { + namespace Detail { + + void TokenStream::loadBuffer() { + m_tokenBuffer.clear(); + + // Skip any empty strings + while ( it != itEnd && it->empty() ) { + ++it; + } + + if ( it != itEnd ) { + StringRef next = *it; + if ( isOptPrefix( next[0] ) ) { + auto delimiterPos = find_first_separator(next); + if ( delimiterPos != StringRef::npos ) { + m_tokenBuffer.push_back( + { TokenType::Option, + next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( + { TokenType::Argument, + next.substr( delimiterPos + 1, next.size() ) } ); + } else { + if ( next.size() > 1 && next[1] != '-' && next.size() > 2 ) { + // Combined short args, e.g. "-ab" for "-a -b" + for ( size_t i = 1; i < next.size(); ++i ) { + m_tokenBuffer.push_back( + { TokenType::Option, + next.substr( i, 1 ) } ); + } + } else { + m_tokenBuffer.push_back( + { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( + { TokenType::Argument, next } ); + } + } + } + + TokenStream::TokenStream( Args const& args ): + TokenStream( args.m_args.begin(), args.m_args.end() ) {} + + TokenStream::TokenStream( Iterator it_, Iterator itEnd_ ): + it( it_ ), itEnd( itEnd_ ) { + loadBuffer(); + } + + TokenStream& TokenStream::operator++() { + if ( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if ( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } + + ParserResult convertInto( std::string const& source, + std::string& target ) { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + + ParserResult convertInto( std::string const& source, + bool& target ) { + std::string srcLC = toLower( source ); + + if ( srcLC == "y" || srcLC == "1" || srcLC == "true" || + srcLC == "yes" || srcLC == "on" ) { + target = true; + } else if ( srcLC == "n" || srcLC == "0" || srcLC == "false" || + srcLC == "no" || srcLC == "off" ) { + target = false; + } else { + return ParserResult::runtimeError( + "Expected a boolean value but did not recognise: '" + + source + '\'' ); + } + return ParserResult::ok( ParseResultType::Matched ); + } + + size_t ParserBase::cardinality() const { return 1; } + + InternalParseResult ParserBase::parse( Args const& args ) const { + return parse( static_cast<std::string>(args.exeName()), TokenStream( args ) ); + } + + ParseState::ParseState( ParseResultType type, + TokenStream remainingTokens ): + m_type( type ), m_remainingTokens( CATCH_MOVE(remainingTokens) ) {} + + ParserResult BoundFlagRef::setFlag( bool flag ) { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); + } + + ResultBase::~ResultBase() = default; + + bool BoundRef::isContainer() const { return false; } + + bool BoundRef::isFlag() const { return false; } + + bool BoundFlagRefBase::isFlag() const { return true; } + +} // namespace Detail + + Detail::InternalParseResult Arg::parse(std::string const&, + Detail::TokenStream tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + auto token = *tokens; + if (token.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::NoMatch, CATCH_MOVE(tokens))); + + assert(!m_ref->isFlag()); + auto valueRef = + static_cast<Detail::BoundValueRefBase*>(m_ref.get()); + + auto result = valueRef->setValue(static_cast<std::string>(token.token)); + if ( !result ) + return Detail::InternalParseResult( result ); + else + return Detail::InternalParseResult::ok( + Detail::ParseState( ParseResultType::Matched, + CATCH_MOVE( ++tokens ) ) ); + } + + Opt::Opt(bool& ref) : + ParserRefImpl(std::make_shared<Detail::BoundFlagRef>(ref)) {} + + Detail::HelpColumns Opt::getHelpColumns() const { + ReusableStringStream oss; + bool first = true; + for (auto const& opt : m_optNames) { + if (first) + first = false; + else + oss << ", "; + oss << opt; + } + if (!m_hint.empty()) + oss << " <" << m_hint << '>'; + return { oss.str(), m_description }; + } + + bool Opt::isMatch(StringRef optToken) const { + auto normalisedToken = normaliseOpt(optToken); + for (auto const& name : m_optNames) { + if (normaliseOpt(name) == normalisedToken) + return true; + } + return false; + } + + Detail::InternalParseResult Opt::parse(std::string const&, + Detail::TokenStream tokens) const { + auto validationResult = validate(); + if (!validationResult) + return Detail::InternalParseResult(validationResult); + + if (tokens && + tokens->type == Detail::TokenType::Option) { + auto const& token = *tokens; + if (isMatch(token.token)) { + if (m_ref->isFlag()) { + auto flagRef = + static_cast<Detail::BoundFlagRefBase*>( + m_ref.get()); + auto result = flagRef->setFlag(true); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == + ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), CATCH_MOVE(tokens))); + } else { + auto valueRef = + static_cast<Detail::BoundValueRefBase*>( + m_ref.get()); + ++tokens; + if (!tokens) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + + token.token); + auto const& argToken = *tokens; + if (argToken.type != Detail::TokenType::Argument) + return Detail::InternalParseResult::runtimeError( + "Expected argument following " + + token.token); + const auto result = valueRef->setValue(static_cast<std::string>(argToken.token)); + if (!result) + return Detail::InternalParseResult(result); + if (result.value() == + ParseResultType::ShortCircuitAll) + return Detail::InternalParseResult::ok(Detail::ParseState( + result.value(), CATCH_MOVE(tokens))); + } + return Detail::InternalParseResult::ok(Detail::ParseState( + ParseResultType::Matched, CATCH_MOVE(++tokens))); + } + } + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); + } + + Detail::Result Opt::validate() const { + if (m_optNames.empty()) + return Detail::Result::logicError("No options supplied to Opt"); + for (auto const& name : m_optNames) { + if (name.empty()) + return Detail::Result::logicError( + "Option name cannot be empty"); +#ifdef CATCH_PLATFORM_WINDOWS + if (name[0] != '-' && name[0] != '/') + return Detail::Result::logicError( + "Option name must begin with '-' or '/'"); +#else + if (name[0] != '-') + return Detail::Result::logicError( + "Option name must begin with '-'"); +#endif + } + return ParserRefImpl::validate(); + } + + ExeName::ExeName() : + m_name(std::make_shared<std::string>("<executable>")) {} + + ExeName::ExeName(std::string& ref) : ExeName() { + m_ref = std::make_shared<Detail::BoundValueRef<std::string>>(ref); + } + + Detail::InternalParseResult + ExeName::parse(std::string const&, + Detail::TokenStream tokens) const { + return Detail::InternalParseResult::ok( + Detail::ParseState(ParseResultType::NoMatch, CATCH_MOVE(tokens))); + } + + ParserResult ExeName::set(std::string const& newName) { + auto lastSlash = newName.find_last_of("\\/"); + auto filename = (lastSlash == std::string::npos) + ? newName + : newName.substr(lastSlash + 1); + + *m_name = filename; + if (m_ref) + return m_ref->setValue(filename); + else + return ParserResult::ok(ParseResultType::Matched); + } + + + + + Parser& Parser::operator|=( Parser const& other ) { + m_options.insert( m_options.end(), + other.m_options.begin(), + other.m_options.end() ); + m_args.insert( + m_args.end(), other.m_args.begin(), other.m_args.end() ); + return *this; + } + + std::vector<Detail::HelpColumns> Parser::getHelpColumns() const { + std::vector<Detail::HelpColumns> cols; + cols.reserve( m_options.size() ); + for ( auto const& o : m_options ) { + cols.push_back(o.getHelpColumns()); + } + return cols; + } + + void Parser::writeToStream( std::ostream& os ) const { + if ( !m_exeName.name().empty() ) { + os << "usage:\n" + << " " << m_exeName.name() << ' '; + bool required = true, first = true; + for ( auto const& arg : m_args ) { + if ( first ) + first = false; + else + os << ' '; + if ( arg.isOptional() && required ) { + os << '['; + required = false; + } + os << '<' << arg.hint() << '>'; + if ( arg.cardinality() == 0 ) + os << " ... "; + } + if ( !required ) + os << ']'; + if ( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:\n"; + } + + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for ( auto const& cols : rows ) + optWidth = ( std::max )( optWidth, cols.left.size() + 2 ); + + optWidth = ( std::min )( optWidth, consoleWidth / 2 ); + + for ( auto& cols : rows ) { + auto row = TextFlow::Column( CATCH_MOVE(cols.left) ) + .width( optWidth ) + .indent( 2 ) + + TextFlow::Spacer( 4 ) + + TextFlow::Column( static_cast<std::string>(cols.descriptions) ) + .width( consoleWidth - 7 - optWidth ); + os << row << '\n'; + } + } + + Detail::Result Parser::validate() const { + for ( auto const& opt : m_options ) { + auto result = opt.validate(); + if ( !result ) + return result; + } + for ( auto const& arg : m_args ) { + auto result = arg.validate(); + if ( !result ) + return result; + } + return Detail::Result::ok(); + } + + Detail::InternalParseResult + Parser::parse( std::string const& exeName, + Detail::TokenStream tokens ) const { + + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + std::vector<ParserInfo> parseInfos; + parseInfos.reserve( m_options.size() + m_args.size() ); + for ( auto const& opt : m_options ) { + parseInfos.push_back( { &opt, 0 } ); + } + for ( auto const& arg : m_args ) { + parseInfos.push_back( { &arg, 0 } ); + } + + m_exeName.set( exeName ); + + auto result = Detail::InternalParseResult::ok( + Detail::ParseState( ParseResultType::NoMatch, CATCH_MOVE(tokens) ) ); + while ( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for ( auto& parseInfo : parseInfos ) { + if ( parseInfo.parser->cardinality() == 0 || + parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse( + exeName, CATCH_MOVE(result).value().remainingTokens() ); + if ( !result ) + return result; + if ( result.value().type() != + ParseResultType::NoMatch ) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } + + if ( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if ( !tokenParsed ) + return Detail::InternalParseResult::runtimeError( + "Unrecognised token: " + + result.value().remainingTokens()->token ); + } + // !TBD Check missing required options + return result; + } + + Args::Args(int argc, char const* const* argv) : + m_exeName(argv[0]), m_args(argv + 1, argv + argc) {} + + Args::Args(std::initializer_list<StringRef> args) : + m_exeName(*args.begin()), + m_args(args.begin() + 1, args.end()) {} + + + Help::Help( bool& showHelpFlag ): + Opt( [&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + } ) { + static_cast<Opt&> ( *this )( + "display usage information" )["-?"]["-h"]["--help"] + .optional(); + } + + } // namespace Clara +} // namespace Catch + + + + +#include <fstream> +#include <string> + +namespace Catch { + + Clara::Parser makeCommandLineParser( ConfigData& config ) { + + using namespace Clara; + + auto const setWarning = [&]( std::string const& warning ) { + if ( warning == "NoAssertions" ) { + config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::NoAssertions); + return ParserResult::ok( ParseResultType::Matched ); + } else if ( warning == "UnmatchedTestSpec" ) { + config.warnings = static_cast<WarnAbout::What>(config.warnings | WarnAbout::UnmatchedTestSpec); + return ParserResult::ok( ParseResultType::Matched ); + } + + return ParserResult ::runtimeError( + "Unrecognised warning option: '" + warning + '\'' ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + '\'' ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + CATCH_MOVE(line) + '"'; + config.testsOrTags.push_back( line ); + config.testsOrTags.emplace_back( "," ); + } + } + //Remove comma in the end + if(!config.testsOrTags.empty()) + config.testsOrTags.erase( config.testsOrTags.end()-1 ); + + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = TestRunOrder::Declared; + else if( startsWith( "lexical", order ) ) + config.runOrder = TestRunOrder::LexicographicallySorted; + else if( startsWith( "random", order ) ) + config.runOrder = TestRunOrder::Randomized; + else + return ParserResult::runtimeError( "Unrecognised ordering: '" + order + '\'' ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = generateRandomSeed(GenerateFrom::Time); + return ParserResult::ok(ParseResultType::Matched); + } else if (seed == "random-device") { + config.rngSeed = generateRandomSeed(GenerateFrom::RandomDevice); + return ParserResult::ok(ParseResultType::Matched); + } + + // TODO: ideally we should be parsing uint32_t directly + // fix this later when we add new parse overload + auto parsedSeed = parseUInt( seed, 0 ); + if ( !parsedSeed ) { + return ParserResult::runtimeError( "Could not parse '" + seed + "' as seed" ); + } + config.rngSeed = *parsedSeed; + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setDefaultColourMode = [&]( std::string const& colourMode ) { + Optional<ColourMode> maybeMode = Catch::Detail::stringToColourMode(toLower( colourMode )); + if ( !maybeMode ) { + return ParserResult::runtimeError( + "colour mode must be one of: default, ansi, win32, " + "or none. '" + + colourMode + "' is not recognised" ); + } + auto mode = *maybeMode; + if ( !isColourImplAvailable( mode ) ) { + return ParserResult::runtimeError( + "colour mode '" + colourMode + + "' is not supported in this binary" ); + } + config.defaultColourMode = mode; + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if (keypressLc == "never") + config.waitForKeypress = WaitForKeypress::Never; + else if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: never, start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + '\'' ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setReporter = [&]( std::string const& userReporterSpec ) { + if ( userReporterSpec.empty() ) { + return ParserResult::runtimeError( "Received empty reporter spec." ); + } + + Optional<ReporterSpec> parsed = + parseReporterSpec( userReporterSpec ); + if ( !parsed ) { + return ParserResult::runtimeError( + "Could not parse reporter spec '" + userReporterSpec + + "'" ); + } + + auto const& reporterSpec = *parsed; + + auto const& factories = + getRegistryHub().getReporterRegistry().getFactories(); + auto result = factories.find( reporterSpec.name() ); + + if ( result == factories.end() ) { + return ParserResult::runtimeError( + "Unrecognized reporter, '" + reporterSpec.name() + + "'. Check available with --list-reporters" ); + } + + + const bool hadOutputFile = reporterSpec.outputFile().some(); + config.reporterSpecifications.push_back( CATCH_MOVE( *parsed ) ); + // It would be enough to check this only once at the very end, but + // there is not a place where we could call this check, so do it + // every time it could fail. For valid inputs, this is still called + // at most once. + if (!hadOutputFile) { + int n_reporters_without_file = 0; + for (auto const& spec : config.reporterSpecifications) { + if (spec.outputFile().none()) { + n_reporters_without_file++; + } + } + if (n_reporters_without_file > 1) { + return ParserResult::runtimeError( "Only one reporter may have unspecified output file." ); + } + } + + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setShardCount = [&]( std::string const& shardCount ) { + auto parsedCount = parseUInt( shardCount ); + if ( !parsedCount ) { + return ParserResult::runtimeError( + "Could not parse '" + shardCount + "' as shard count" ); + } + if ( *parsedCount == 0 ) { + return ParserResult::runtimeError( + "Shard count must be positive" ); + } + config.shardCount = *parsedCount; + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto const setShardIndex = [&](std::string const& shardIndex) { + auto parsedIndex = parseUInt( shardIndex ); + if ( !parsedIndex ) { + return ParserResult::runtimeError( + "Could not parse '" + shardIndex + "' as shard index" ); + } + config.shardIndex = *parsedIndex; + return ParserResult::ok( ParseResultType::Matched ); + }; + + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.defaultOutputFilename, "filename" ) + ["-o"]["--out"] + ( "default output filename" ) + | Opt( accept_many, setReporter, "name[::key=value]*" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( accept_many, setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( config.minDuration, "seconds" ) + ["-D"]["--min-duration"] + ( "show test durations for tests taking at least the given number of seconds" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTests ) + ["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all available reporters" ) + | Opt( config.listListeners ) + ["--list-listeners"] + ( "list all listeners" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|'random-device'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setDefaultColourMode, "ansi|win32|none|default" ) + ["--colour-mode"] + ( "what color mode should be used as default" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "never|start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.skipBenchmarks) + ["--skip-benchmarks"] + ( "disable running benchmarks") + | Opt( config.benchmarkSamples, "samples" ) + ["--benchmark-samples"] + ( "number of samples to collect (default: 100)" ) + | Opt( config.benchmarkResamples, "resamples" ) + ["--benchmark-resamples"] + ( "number of resamples for the bootstrap (default: 100000)" ) + | Opt( config.benchmarkConfidenceInterval, "confidence interval" ) + ["--benchmark-confidence-interval"] + ( "confidence interval for the bootstrap (between 0 and 1, default: 0.95)" ) + | Opt( config.benchmarkNoAnalysis ) + ["--benchmark-no-analysis"] + ( "perform only measurements; do not perform any analysis" ) + | Opt( config.benchmarkWarmupTime, "benchmarkWarmupTime" ) + ["--benchmark-warmup-time"] + ( "amount of time in milliseconds spent on warming up each test (default: 100)" ) + | Opt( setShardCount, "shard count" ) + ["--shard-count"] + ( "split the tests to execute into this many groups" ) + | Opt( setShardIndex, "shard index" ) + ["--shard-index"] + ( "index of the group of tests to execute (see --shard-count)" ) + | Opt( config.allowZeroTests ) + ["--allow-running-no-tests"] + ( "Treat 'No tests run' as a success" ) + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); + + return cli; + } + +} // end namespace Catch + + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + + + +#include <cassert> +#include <ostream> +#include <utility> + +namespace Catch { + + ColourImpl::~ColourImpl() = default; + + ColourImpl::ColourGuard ColourImpl::guardColour( Colour::Code colourCode ) { + return ColourGuard(colourCode, this ); + } + + void ColourImpl::ColourGuard::engageImpl( std::ostream& stream ) { + assert( &stream == &m_colourImpl->m_stream->stream() && + "Engaging colour guard for different stream than used by the " + "parent colour implementation" ); + static_cast<void>( stream ); + + m_engaged = true; + m_colourImpl->use( m_code ); + } + + ColourImpl::ColourGuard::ColourGuard( Colour::Code code, + ColourImpl const* colour ): + m_colourImpl( colour ), m_code( code ) { + } + ColourImpl::ColourGuard::ColourGuard( ColourGuard&& rhs ) noexcept: + m_colourImpl( rhs.m_colourImpl ), + m_code( rhs.m_code ), + m_engaged( rhs.m_engaged ) { + rhs.m_engaged = false; + } + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::operator=( ColourGuard&& rhs ) noexcept { + using std::swap; + swap( m_colourImpl, rhs.m_colourImpl ); + swap( m_code, rhs.m_code ); + swap( m_engaged, rhs.m_engaged ); + + return *this; + } + ColourImpl::ColourGuard::~ColourGuard() { + if ( m_engaged ) { + m_colourImpl->use( Colour::None ); + } + } + + ColourImpl::ColourGuard& + ColourImpl::ColourGuard::engage( std::ostream& stream ) & { + engageImpl( stream ); + return *this; + } + + ColourImpl::ColourGuard&& + ColourImpl::ColourGuard::engage( std::ostream& stream ) && { + engageImpl( stream ); + return CATCH_MOVE(*this); + } + + namespace { + //! A do-nothing implementation of colour, used as fallback for unknown + //! platforms, and when the user asks to deactivate all colours. + class NoColourImpl final : public ColourImpl { + public: + NoColourImpl( IStream* stream ): ColourImpl( stream ) {} + + private: + void use( Colour::Code ) const override {} + }; + } // namespace + + +} // namespace Catch + + +#if defined ( CATCH_CONFIG_COLOUR_WIN32 ) ///////////////////////////////////////// + +namespace Catch { +namespace { + + class Win32ColourImpl final : public ColourImpl { + public: + Win32ColourImpl(IStream* stream): + ColourImpl(stream) { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), + &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } + + static bool useImplementationForStream(IStream const& stream) { + // Win32 text colour APIs can only be used on console streams + // We cannot check that the output hasn't been redirected, + // so we just check that the original stream is console stream. + return stream.isConsole(); + } + + private: + void use( Colour::Code _colourCode ) const override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::BrightYellow: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + + default: + CATCH_ERROR( "Unknown colour requested" ); + } + } + + void setTextAttribute( WORD _textAttribute ) const { + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), + _textAttribute | + originalBackgroundAttributes ); + } + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; + +} // end anon namespace +} // end namespace Catch + +#endif // Windows/ ANSI/ None + + +#if defined( CATCH_PLATFORM_LINUX ) || defined( CATCH_PLATFORM_MAC ) || defined( __GLIBC__ ) +# define CATCH_INTERNAL_HAS_ISATTY +# include <unistd.h> +#endif + +namespace Catch { +namespace { + + class ANSIColourImpl final : public ColourImpl { + public: + ANSIColourImpl( IStream* stream ): ColourImpl( stream ) {} + + static bool useImplementationForStream(IStream const& stream) { + // This is kinda messy due to trying to support a bunch of + // different platforms at once. + // The basic idea is that if we are asked to do autodetection (as + // opposed to being told to use posixy colours outright), then we + // only want to use the colours if we are writing to console. + // However, console might be redirected, so we make an attempt at + // checking for that on platforms where we know how to do that. + bool useColour = stream.isConsole(); +#if defined( CATCH_INTERNAL_HAS_ISATTY ) && \ + !( defined( __DJGPP__ ) && defined( __STRICT_ANSI__ ) ) + ErrnoGuard _; // for isatty + useColour = useColour && isatty( STDOUT_FILENO ); +# endif +# if defined( CATCH_PLATFORM_MAC ) || defined( CATCH_PLATFORM_IPHONE ) + useColour = useColour && !isDebuggerActive(); +# endif + + return useColour; + } + + private: + void use( Colour::Code _colourCode ) const override { + auto setColour = [&out = + m_stream->stream()]( char const* escapeCode ) { + // The escape sequence must be flushed to console, otherwise + // if stdin and stderr are intermixed, we'd get accidentally + // coloured output. + out << '\033' << escapeCode << std::flush; + }; + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + case Colour::BrightYellow: return setColour( "[1;33m" ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + default: CATCH_INTERNAL_ERROR( "Unknown colour requested" ); + } + } + }; + +} // end anon namespace +} // end namespace Catch + +namespace Catch { + + Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode colourSelection, + IStream* stream ) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + if ( colourSelection == ColourMode::Win32 ) { + return Detail::make_unique<Win32ColourImpl>( stream ); + } +#endif + if ( colourSelection == ColourMode::ANSI ) { + return Detail::make_unique<ANSIColourImpl>( stream ); + } + if ( colourSelection == ColourMode::None ) { + return Detail::make_unique<NoColourImpl>( stream ); + } + + if ( colourSelection == ColourMode::PlatformDefault) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + if ( Win32ColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique<Win32ColourImpl>( stream ); + } +#endif + if ( ANSIColourImpl::useImplementationForStream( *stream ) ) { + return Detail::make_unique<ANSIColourImpl>( stream ); + } + return Detail::make_unique<NoColourImpl>( stream ); + } + + CATCH_ERROR( "Could not create colour impl for selection " << static_cast<int>(colourSelection) ); + } + + bool isColourImplAvailable( ColourMode colourSelection ) { + switch ( colourSelection ) { +#if defined( CATCH_CONFIG_COLOUR_WIN32 ) + case ColourMode::Win32: +#endif + case ColourMode::ANSI: + case ColourMode::None: + case ColourMode::PlatformDefault: + return true; + default: + return false; + } + } + + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + + +namespace Catch { + + Context* Context::currentContext = nullptr; + + void cleanUpContext() { + delete Context::currentContext; + Context::currentContext = nullptr; + } + void Context::createContext() { + currentContext = new Context(); + } + + Context& getCurrentMutableContext() { + if ( !Context::currentContext ) { Context::createContext(); } + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *Context::currentContext; + } + + SimplePcg32& sharedRng() { + static SimplePcg32 s_rng; + return s_rng; + } + +} + + + + + +#include <ostream> + +#if defined(CATCH_CONFIG_ANDROID_LOGWRITE) +#include <android/log.h> + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + __android_log_write( ANDROID_LOG_DEBUG, "Catch", text.c_str() ); + } + } + +#elif defined(CATCH_PLATFORM_WINDOWS) + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } + +#else + + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } + +#endif // Platform + + + +#if defined(CATCH_PLATFORM_MAC) || defined(CATCH_PLATFORM_IPHONE) + +# include <cassert> +# include <sys/types.h> +# include <unistd.h> +# include <cstddef> +# include <ostream> + +#ifdef __apple_build_version__ + // These headers will only compile with AppleClang (XCode) + // For other compilers (Clang, GCC, ... ) we need to exclude them +# include <sys/sysctl.h> +#endif + + namespace Catch { + #ifdef __apple_build_version__ + // The following function is taken directly from the following technical note: + // https://developer.apple.com/library/archive/qa/qa1361/_index.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + int mib[4]; + struct kinfo_proc info; + std::size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n\n" << std::flush; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + #else + bool isDebuggerActive() { + // We need to find another way to determine this for non-appleclang compilers on macOS + return false; + } + #endif + } // namespace Catch + +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> + + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform + + + + +namespace Catch { + + void ITransientExpression::streamReconstructedExpression( + std::ostream& os ) const { + // We can't make this function pure virtual to keep ITransientExpression + // constexpr, so we write error message instead + os << "Some class derived from ITransientExpression without overriding streamReconstructedExpression"; + } + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << ' ' << op << ' ' << rhs; + else + os << lhs << '\n' << op << '\n' << rhs; + } +} + + + +#include <stdexcept> + + +namespace Catch { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS_CUSTOM_HANDLER) + [[noreturn]] + void throw_exception(std::exception const& e) { + Catch::cerr() << "Catch will terminate because it needed to throw an exception.\n" + << "The message was: " << e.what() << '\n'; + std::terminate(); + } +#endif + + [[noreturn]] + void throw_logic_error(std::string const& msg) { + throw_exception(std::logic_error(msg)); + } + + [[noreturn]] + void throw_domain_error(std::string const& msg) { + throw_exception(std::domain_error(msg)); + } + + [[noreturn]] + void throw_runtime_error(std::string const& msg) { + throw_exception(std::runtime_error(msg)); + } + + + +} // namespace Catch; + + + +#include <cassert> + +namespace Catch { + + IMutableEnumValuesRegistry::~IMutableEnumValuesRegistry() = default; + + namespace Detail { + + namespace { + // Extracts the actual name part of an enum instance + // In other words, it returns the Blue part of Bikeshed::Colour::Blue + StringRef extractInstanceName(StringRef enumInstance) { + // Find last occurrence of ":" + size_t name_start = enumInstance.size(); + while (name_start > 0 && enumInstance[name_start - 1] != ':') { + --name_start; + } + return enumInstance.substr(name_start, enumInstance.size() - name_start); + } + } + + std::vector<StringRef> parseEnums( StringRef enums ) { + auto enumValues = splitStringRef( enums, ',' ); + std::vector<StringRef> parsed; + parsed.reserve( enumValues.size() ); + for( auto const& enumValue : enumValues ) { + parsed.push_back(trim(extractInstanceName(enumValue))); + } + return parsed; + } + + EnumInfo::~EnumInfo() = default; + + StringRef EnumInfo::lookup( int value ) const { + for( auto const& valueToName : m_values ) { + if( valueToName.first == value ) + return valueToName.second; + } + return "{** unexpected enum value **}"_sr; + } + + Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + auto enumInfo = Catch::Detail::make_unique<EnumInfo>(); + enumInfo->m_name = enumName; + enumInfo->m_values.reserve( values.size() ); + + const auto valueNames = Catch::Detail::parseEnums( allValueNames ); + assert( valueNames.size() == values.size() ); + std::size_t i = 0; + for( auto value : values ) + enumInfo->m_values.emplace_back(value, valueNames[i++]); + + return enumInfo; + } + + EnumInfo const& EnumValuesRegistry::registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ) { + m_enumInfos.push_back(makeEnumInfo(enumName, allValueNames, values)); + return *m_enumInfos.back(); + } + + } // Detail +} // Catch + + + + + +#include <cerrno> + +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } +} + + + +#include <exception> + +namespace Catch { + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + namespace { + static std::string tryTranslators( + std::vector< + Detail::unique_ptr<IExceptionTranslator const>> const& translators ) { + if ( translators.empty() ) { + std::rethrow_exception( std::current_exception() ); + } else { + return translators[0]->translate( translators.begin() + 1, + translators.end() ); + } + } + + } +#endif //!defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() = default; + + void ExceptionTranslatorRegistry::registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) { + m_translators.push_back( CATCH_MOVE( translator ) ); + } + +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + std::string ExceptionTranslatorRegistry::translateActiveException() const { + // Compiling a mixed mode project with MSVC means that CLR + // exceptions will be caught in (...) as well. However, these do + // do not fill-in std::current_exception and thus lead to crash + // when attempting rethrow. + // /EHa switch also causes structured exceptions to be caught + // here, but they fill-in current_exception properly, so + // at worst the output should be a little weird, instead of + // causing a crash. + if ( std::current_exception() == nullptr ) { + return "Non C++ exception. Possibly a CLR exception."; + } + + // First we try user-registered translators. If none of them can + // handle the exception, it will be rethrown handled by our defaults. + try { + return tryTranslators(m_translators); + } + // To avoid having to handle TFE explicitly everywhere, we just + // rethrow it so that it goes back up the caller. + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( TestSkipException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception const& ex ) { + return ex.what(); + } + catch( std::string const& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } + } + +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + std::string ExceptionTranslatorRegistry::translateActiveException() const { + CATCH_INTERNAL_ERROR("Attempted to translate active exception under CATCH_CONFIG_DISABLE_EXCEPTIONS!"); + } +#endif + +} + + + +/** \file + * This file provides platform specific implementations of FatalConditionHandler + * + * This means that there is a lot of conditional compilation, and platform + * specific code. Currently, Catch2 supports a dummy handler (if no + * handler is desired), and 2 platform specific handlers: + * * Windows' SEH + * * POSIX signals + * + * Consequently, various pieces of code below are compiled if either of + * the platform specific handlers is enabled, or if none of them are + * enabled. It is assumed that both cannot be enabled at the same time, + * and doing so should cause a compilation error. + * + * If another platform specific handler is added, the compile guards + * below will need to be updated taking these assumptions into account. + */ + + + +#include <algorithm> + +#if !defined( CATCH_CONFIG_WINDOWS_SEH ) && !defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace Catch { + + // If neither SEH nor signal handling is required, the handler impls + // do not have to do anything, and can be empty. + void FatalConditionHandler::engage_platform() {} + void FatalConditionHandler::disengage_platform() noexcept {} + FatalConditionHandler::FatalConditionHandler() = default; + FatalConditionHandler::~FatalConditionHandler() = default; + +} // end namespace Catch + +#endif // !CATCH_CONFIG_WINDOWS_SEH && !CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) && defined( CATCH_CONFIG_POSIX_SIGNALS ) +#error "Inconsistent configuration: Windows' SEH handling and POSIX signals cannot be enabled at the same time" +#endif // CATCH_CONFIG_WINDOWS_SEH && CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) || defined( CATCH_CONFIG_POSIX_SIGNALS ) + +namespace { + //! Signals fatal error message to the run context + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); + } + + //! Minimal size Catch2 needs for its own fatal error handling. + //! Picked empirically, so it might not be sufficient on all + //! platforms, and for all configurations. + constexpr std::size_t minStackSizeForErrors = 32 * 1024; +} // end unnamed namespace + +#endif // CATCH_CONFIG_WINDOWS_SEH || CATCH_CONFIG_POSIX_SIGNALS + +#if defined( CATCH_CONFIG_WINDOWS_SEH ) + +namespace Catch { + + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; + + static LONG CALLBACK topLevelExceptionFilter(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } + } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } + + // Since we do not support multiple instantiations, we put these + // into global variables and rely on cleaning them up in outlined + // constructors/destructors + static LPTOP_LEVEL_EXCEPTION_FILTER previousTopLevelExceptionFilter = nullptr; + + + // For MSVC, we reserve part of the stack memory for handling + // memory overflow structured exception. + FatalConditionHandler::FatalConditionHandler() { + ULONG guaranteeSize = static_cast<ULONG>(minStackSizeForErrors); + if (!SetThreadStackGuarantee(&guaranteeSize)) { + // We do not want to fully error out, because needing + // the stack reserve should be rare enough anyway. + Catch::cerr() + << "Failed to reserve piece of stack." + << " Stack overflows will not be reported successfully."; + } + } + + // We do not attempt to unset the stack guarantee, because + // Windows does not support lowering the stack size guarantee. + FatalConditionHandler::~FatalConditionHandler() = default; + + + void FatalConditionHandler::engage_platform() { + // Register as a the top level exception filter. + previousTopLevelExceptionFilter = SetUnhandledExceptionFilter(topLevelExceptionFilter); + } + + void FatalConditionHandler::disengage_platform() noexcept { + if (SetUnhandledExceptionFilter(previousTopLevelExceptionFilter) != topLevelExceptionFilter) { + Catch::cerr() + << "Unexpected SEH unhandled exception filter on disengage." + << " The filter was restored, but might be rolled back unexpectedly."; + } + previousTopLevelExceptionFilter = nullptr; + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_WINDOWS_SEH + +#if defined( CATCH_CONFIG_POSIX_SIGNALS ) + +#include <signal.h> + +namespace Catch { + + struct SignalDefs { + int id; + const char* name; + }; + + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + +// Older GCCs trigger -Wmissing-field-initializers for T foo = {} +// which is zero initialization, but not explicit. We want to avoid +// that. +#if defined(__GNUC__) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmissing-field-initializers" +#endif + + static char* altStackMem = nullptr; + static std::size_t altStackSize = 0; + static stack_t oldSigStack{}; + static struct sigaction oldSigActions[sizeof(signalDefs) / sizeof(SignalDefs)]{}; + + static void restorePreviousSignalHandlers() noexcept { + // We set signal handlers back to the previous ones. Hopefully + // nobody overwrote them in the meantime, and doesn't expect + // their signal handlers to live past ours given that they + // installed them after ours.. + for (std::size_t i = 0; i < sizeof(signalDefs) / sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + } + + static void handleSignal( int sig ) { + char const * name = "<unknown signal>"; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; + } + } + // We need to restore previous signal handlers and let them do + // their thing, so that the users can have the debugger break + // when a signal is raised, and so on. + restorePreviousSignalHandlers(); + reportFatal( name ); + raise( sig ); + } + + FatalConditionHandler::FatalConditionHandler() { + assert(!altStackMem && "Cannot initialize POSIX signal handler when one already exists"); + if (altStackSize == 0) { + altStackSize = std::max(static_cast<size_t>(SIGSTKSZ), minStackSizeForErrors); + } + altStackMem = new char[altStackSize](); + } + + FatalConditionHandler::~FatalConditionHandler() { + delete[] altStackMem; + // We signal that another instance can be constructed by zeroing + // out the pointer. + altStackMem = nullptr; + } + + void FatalConditionHandler::engage_platform() { + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = altStackSize; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; + + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); + } + } + +#if defined(__GNUC__) +# pragma GCC diagnostic pop +#endif + + + void FatalConditionHandler::disengage_platform() noexcept { + restorePreviousSignalHandlers(); + } + +} // end namespace Catch + +#endif // CATCH_CONFIG_POSIX_SIGNALS + + + + +#include <cstring> + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f) { + static_assert(sizeof(float) == sizeof(uint32_t), "Important ULP matcher assumption violated"); + uint32_t i; + std::memcpy(&i, &f, sizeof(f)); + return i; + } + + uint64_t convertToBits(double d) { + static_assert(sizeof(double) == sizeof(uint64_t), "Important ULP matcher assumption violated"); + uint64_t i; + std::memcpy(&i, &d, sizeof(d)); + return i; + } + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + bool directCompare( float lhs, float rhs ) { return lhs == rhs; } + bool directCompare( double lhs, double rhs ) { return lhs == rhs; } +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + + + } // end namespace Detail +} // end namespace Catch + + + + + + +#include <cstdlib> + +namespace Catch { + namespace Detail { + +#if !defined (CATCH_CONFIG_GETENV) + char const* getEnv( char const* ) { return nullptr; } +#else + + char const* getEnv( char const* varName ) { +# if defined( _MSC_VER ) +# pragma warning( push ) +# pragma warning( disable : 4996 ) // use getenv_s instead of getenv +# endif + + return std::getenv( varName ); + +# if defined( _MSC_VER ) +# pragma warning( pop ) +# endif + } +#endif +} // namespace Detail +} // namespace Catch + + + + +#include <cstdio> +#include <fstream> +#include <sstream> +#include <vector> + +namespace Catch { + + Catch::IStream::~IStream() = default; + +namespace Detail { + namespace { + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl final : public std::streambuf { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept override { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const& str ) { + if ( !str.empty() ) { + writeToDebugConsole( str ); + } + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class FileStream final : public IStream { + std::ofstream m_ofs; + public: + FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << '\'' ); + m_ofs << std::unitbuf; + } + public: // IStream + std::ostream& stream() override { + return m_ofs; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + class CoutStream final : public IStream { + std::ostream m_os; + public: + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream() : m_os( Catch::cout().rdbuf() ) {} + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + class CerrStream : public IStream { + std::ostream m_os; + + public: + // Store the streambuf from cerr up-front because + // cout may get redirected when running tests + CerrStream(): m_os( Catch::cerr().rdbuf() ) {} + + public: // IStream + std::ostream& stream() override { return m_os; } + bool isConsole() const override { return true; } + }; + + /////////////////////////////////////////////////////////////////////////// + + class DebugOutStream final : public IStream { + Detail::unique_ptr<StreamBufImpl<OutputDebugWriter>> m_streamBuf; + std::ostream m_os; + public: + DebugOutStream() + : m_streamBuf( Detail::make_unique<StreamBufImpl<OutputDebugWriter>>() ), + m_os( m_streamBuf.get() ) + {} + + public: // IStream + std::ostream& stream() override { return m_os; } + }; + + } // unnamed namespace +} // namespace Detail + + /////////////////////////////////////////////////////////////////////////// + + auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream> { + if ( filename.empty() || filename == "-" ) { + return Detail::make_unique<Detail::CoutStream>(); + } + if( filename[0] == '%' ) { + if ( filename == "%debug" ) { + return Detail::make_unique<Detail::DebugOutStream>(); + } else if ( filename == "%stderr" ) { + return Detail::make_unique<Detail::CerrStream>(); + } else if ( filename == "%stdout" ) { + return Detail::make_unique<Detail::CoutStream>(); + } else { + CATCH_ERROR( "Unrecognised stream: '" << filename << '\'' ); + } + } + return Detail::make_unique<Detail::FileStream>( filename ); + } + +} + + + +namespace Catch { + void JsonUtils::indent( std::ostream& os, std::uint64_t level ) { + for ( std::uint64_t i = 0; i < level; ++i ) { + os << " "; + } + } + void JsonUtils::appendCommaNewline( std::ostream& os, + bool& should_comma, + std::uint64_t level ) { + if ( should_comma ) { os << ','; } + should_comma = true; + os << '\n'; + indent( os, level ); + } + + JsonObjectWriter::JsonObjectWriter( std::ostream& os ): + JsonObjectWriter{ os, 0 } {} + + JsonObjectWriter::JsonObjectWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } { + m_os << '{'; + } + JsonObjectWriter::JsonObjectWriter( JsonObjectWriter&& source ) noexcept: + m_os{ source.m_os }, + m_indent_level{ source.m_indent_level }, + m_should_comma{ source.m_should_comma }, + m_active{ source.m_active } { + source.m_active = false; + } + + JsonObjectWriter::~JsonObjectWriter() { + if ( !m_active ) { return; } + + m_os << '\n'; + JsonUtils::indent( m_os, m_indent_level ); + m_os << '}'; + } + + JsonValueWriter JsonObjectWriter::write( StringRef key ) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + + m_os << '"' << key << "\": "; + return JsonValueWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter::JsonArrayWriter( std::ostream& os ): + JsonArrayWriter{ os, 0 } {} + JsonArrayWriter::JsonArrayWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } { + m_os << '['; + } + JsonArrayWriter::JsonArrayWriter( JsonArrayWriter&& source ) noexcept: + m_os{ source.m_os }, + m_indent_level{ source.m_indent_level }, + m_should_comma{ source.m_should_comma }, + m_active{ source.m_active } { + source.m_active = false; + } + JsonArrayWriter::~JsonArrayWriter() { + if ( !m_active ) { return; } + + m_os << '\n'; + JsonUtils::indent( m_os, m_indent_level ); + m_os << ']'; + } + + JsonObjectWriter JsonArrayWriter::writeObject() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + return JsonObjectWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter JsonArrayWriter::writeArray() { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + return JsonArrayWriter{ m_os, m_indent_level + 1 }; + } + + JsonArrayWriter& JsonArrayWriter::write( bool value ) { + return writeImpl( value ); + } + + JsonValueWriter::JsonValueWriter( std::ostream& os ): + JsonValueWriter{ os, 0 } {} + + JsonValueWriter::JsonValueWriter( std::ostream& os, + std::uint64_t indent_level ): + m_os{ os }, m_indent_level{ indent_level } {} + + JsonObjectWriter JsonValueWriter::writeObject() && { + return JsonObjectWriter{ m_os, m_indent_level }; + } + + JsonArrayWriter JsonValueWriter::writeArray() && { + return JsonArrayWriter{ m_os, m_indent_level }; + } + + void JsonValueWriter::write( Catch::StringRef value ) && { + writeImpl( value, true ); + } + + void JsonValueWriter::write( bool value ) && { + writeImpl( value ? "true"_sr : "false"_sr, false ); + } + + void JsonValueWriter::writeImpl( Catch::StringRef value, bool quote ) { + if ( quote ) { m_os << '"'; } + for (char c : value) { + // Escape list taken from https://www.json.org/json-en.html, + // string definition. + // Note that while forward slash _can_ be escaped, it does + // not have to be, if JSON is not further embedded somewhere + // where forward slash is meaningful. + if ( c == '"' ) { + m_os << "\\\""; + } else if ( c == '\\' ) { + m_os << "\\\\"; + } else if ( c == '\b' ) { + m_os << "\\b"; + } else if ( c == '\f' ) { + m_os << "\\f"; + } else if ( c == '\n' ) { + m_os << "\\n"; + } else if ( c == '\r' ) { + m_os << "\\r"; + } else if ( c == '\t' ) { + m_os << "\\t"; + } else { + m_os << c; + } + } + if ( quote ) { m_os << '"'; } + } + +} // namespace Catch + + + + +namespace Catch { + + auto operator << (std::ostream& os, LazyExpression const& lazyExpr) -> std::ostream& { + if (lazyExpr.m_isNegated) + os << '!'; + + if (lazyExpr) { + if (lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression()) + os << '(' << *lazyExpr.m_transientExpression << ')'; + else + os << *lazyExpr.m_transientExpression; + } else { + os << "{** error - unchecked empty expression requested **}"; + } + return os; + } + +} // namespace Catch + + + + +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> + +namespace Catch { + + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } +} + +#else // ^^ Windows crt debug heap enabled // Windows crt debug heap disabled vv + + Catch::LeakDetector::LeakDetector() = default; + +#endif // CATCH_CONFIG_WINDOWS_CRTDBG + +Catch::LeakDetector::~LeakDetector() { + Catch::cleanUp(); +} + + + + +namespace Catch { + namespace { + + void listTests(IEventListener& reporter, IConfig const& config) { + auto const& testSpec = config.testSpec(); + auto matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + reporter.listTests(matchedTestCases); + } + + void listTags(IEventListener& reporter, IConfig const& config) { + auto const& testSpec = config.testSpec(); + std::vector<TestCaseHandle> matchedTestCases = filterTests(getAllTestCasesSorted(config), testSpec, config); + + std::map<StringRef, TagInfo, Detail::CaseInsensitiveLess> tagCounts; + for (auto const& testCase : matchedTestCases) { + for (auto const& tagName : testCase.getTestCaseInfo().tags) { + auto it = tagCounts.find(tagName.original); + if (it == tagCounts.end()) + it = tagCounts.insert(std::make_pair(tagName.original, TagInfo())).first; + it->second.add(tagName.original); + } + } + + std::vector<TagInfo> infos; infos.reserve(tagCounts.size()); + for (auto& tagc : tagCounts) { + infos.push_back(CATCH_MOVE(tagc.second)); + } + + reporter.listTags(infos); + } + + void listReporters(IEventListener& reporter) { + std::vector<ReporterDescription> descriptions; + + auto const& factories = getRegistryHub().getReporterRegistry().getFactories(); + descriptions.reserve(factories.size()); + for (auto const& fac : factories) { + descriptions.push_back({ fac.first, fac.second->getDescription() }); + } + + reporter.listReporters(descriptions); + } + + void listListeners(IEventListener& reporter) { + std::vector<ListenerDescription> descriptions; + + auto const& factories = + getRegistryHub().getReporterRegistry().getListeners(); + descriptions.reserve( factories.size() ); + for ( auto const& fac : factories ) { + descriptions.push_back( { fac->getName(), fac->getDescription() } ); + } + + reporter.listListeners( descriptions ); + } + + } // end anonymous namespace + + void TagInfo::add( StringRef spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + // 2 per tag for brackets '[' and ']' + size_t size = spellings.size() * 2; + for (auto const& spelling : spellings) { + size += spelling.size(); + } + + std::string out; out.reserve(size); + for (auto const& spelling : spellings) { + out += '['; + out += spelling; + out += ']'; + } + return out; + } + + bool list( IEventListener& reporter, Config const& config ) { + bool listed = false; + if (config.listTests()) { + listed = true; + listTests(reporter, config); + } + if (config.listTags()) { + listed = true; + listTags(reporter, config); + } + if (config.listReporters()) { + listed = true; + listReporters(reporter); + } + if ( config.listListeners() ) { + listed = true; + listListeners( reporter ); + } + return listed; + } + +} // end namespace Catch + + + +namespace Catch { + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS + static LeakDetector leakDetector; + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +} + +// Allow users of amalgamated .cpp file to remove our main and provide their own. +#if !defined(CATCH_AMALGAMATED_CUSTOM_MAIN) + +#if defined(CATCH_CONFIG_WCHAR) && defined(CATCH_PLATFORM_WINDOWS) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int __cdecl wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else +// Standard C/C++ main entry point +int main (int argc, char * argv[]) { +#endif + + // We want to force the linker not to discard the global variable + // and its constructor, as it (optionally) registers leak detector + (void)&Catch::leakDetector; + + return Catch::Session().run( argc, argv ); +} + +#endif // !defined(CATCH_AMALGAMATED_CUSTOM_MAIN + + + + +namespace Catch { + + MessageInfo::MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + +} // end namespace Catch + + + +#include <cstdio> +#include <cstring> +#include <iosfwd> +#include <sstream> + +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( _MSC_VER ) +# include <io.h> //_dup and _dup2 +# define dup _dup +# define dup2 _dup2 +# define fileno _fileno +# else +# include <unistd.h> // dup and dup2 +# endif +#endif + +namespace Catch { + + namespace { + //! A no-op implementation, used if no reporter wants output + //! redirection. + class NoopRedirect : public OutputRedirect { + void activateImpl() override {} + void deactivateImpl() override {} + std::string getStdout() override { return {}; } + std::string getStderr() override { return {}; } + void clearBuffers() override {} + }; + + /** + * Redirects specific stream's rdbuf with another's. + * + * Redirection can be stopped and started on-demand, assumes + * that the underlying stream's rdbuf aren't changed by other + * users. + */ + class RedirectedStreamNew { + std::ostream& m_originalStream; + std::ostream& m_redirectionStream; + std::streambuf* m_prevBuf; + + public: + RedirectedStreamNew( std::ostream& originalStream, + std::ostream& redirectionStream ): + m_originalStream( originalStream ), + m_redirectionStream( redirectionStream ), + m_prevBuf( m_originalStream.rdbuf() ) {} + + void startRedirect() { + m_originalStream.rdbuf( m_redirectionStream.rdbuf() ); + } + void stopRedirect() { m_originalStream.rdbuf( m_prevBuf ); } + }; + + /** + * Redirects the `std::cout`, `std::cerr`, `std::clog` streams, + * but does not touch the actual `stdout`/`stderr` file descriptors. + */ + class StreamRedirect : public OutputRedirect { + ReusableStringStream m_redirectedOut, m_redirectedErr; + RedirectedStreamNew m_cout, m_cerr, m_clog; + + public: + StreamRedirect(): + m_cout( Catch::cout(), m_redirectedOut.get() ), + m_cerr( Catch::cerr(), m_redirectedErr.get() ), + m_clog( Catch::clog(), m_redirectedErr.get() ) {} + + void activateImpl() override { + m_cout.startRedirect(); + m_cerr.startRedirect(); + m_clog.startRedirect(); + } + void deactivateImpl() override { + m_cout.stopRedirect(); + m_cerr.stopRedirect(); + m_clog.stopRedirect(); + } + std::string getStdout() override { return m_redirectedOut.str(); } + std::string getStderr() override { return m_redirectedErr.str(); } + void clearBuffers() override { + m_redirectedOut.str( "" ); + m_redirectedErr.str( "" ); + } + }; + +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + + // Windows's implementation of std::tmpfile is terrible (it tries + // to create a file inside system folder, thus requiring elevated + // privileges for the binary), so we have to use tmpnam(_s) and + // create the file ourselves there. + class TempFile { + public: + TempFile( TempFile const& ) = delete; + TempFile& operator=( TempFile const& ) = delete; + TempFile( TempFile&& ) = delete; + TempFile& operator=( TempFile&& ) = delete; + +# if defined( _MSC_VER ) + TempFile() { + if ( tmpnam_s( m_buffer ) ) { + CATCH_RUNTIME_ERROR( "Could not get a temp filename" ); + } + if ( fopen_s( &m_file, m_buffer, "wb+" ) ) { + char buffer[100]; + if ( strerror_s( buffer, errno ) ) { + CATCH_RUNTIME_ERROR( + "Could not translate errno to a string" ); + } + CATCH_RUNTIME_ERROR( "Could not open the temp file: '" + << m_buffer + << "' because: " << buffer ); + } + } +# else + TempFile() { + m_file = std::tmpfile(); + if ( !m_file ) { + CATCH_RUNTIME_ERROR( "Could not create a temp file." ); + } + } +# endif + + ~TempFile() { + // TBD: What to do about errors here? + std::fclose( m_file ); + // We manually create the file on Windows only, on Linux + // it will be autodeleted +# if defined( _MSC_VER ) + std::remove( m_buffer ); +# endif + } + + std::FILE* getFile() { return m_file; } + std::string getContents() { + ReusableStringStream sstr; + constexpr long buffer_size = 100; + char buffer[buffer_size + 1] = {}; + long current_pos = ftell( m_file ); + CATCH_ENFORCE( current_pos >= 0, + "ftell failed, errno: " << errno ); + std::rewind( m_file ); + while ( current_pos > 0 ) { + auto read_characters = + std::fread( buffer, + 1, + std::min( buffer_size, current_pos ), + m_file ); + buffer[read_characters] = '\0'; + sstr << buffer; + current_pos -= static_cast<long>( read_characters ); + } + return sstr.str(); + } + + void clear() { std::rewind( m_file ); } + + private: + std::FILE* m_file = nullptr; + char m_buffer[L_tmpnam] = { 0 }; + }; + + /** + * Redirects the actual `stdout`/`stderr` file descriptors. + * + * Works by replacing the file descriptors numbered 1 and 2 + * with an open temporary file. + */ + class FileRedirect : public OutputRedirect { + TempFile m_outFile, m_errFile; + int m_originalOut = -1; + int m_originalErr = -1; + + // Flushes cout/cerr/clog streams and stdout/stderr FDs + void flushEverything() { + Catch::cout() << std::flush; + fflush( stdout ); + // Since we support overriding these streams, we flush cerr + // even though std::cerr is unbuffered + Catch::cerr() << std::flush; + Catch::clog() << std::flush; + fflush( stderr ); + } + + public: + FileRedirect(): + m_originalOut( dup( fileno( stdout ) ) ), + m_originalErr( dup( fileno( stderr ) ) ) { + CATCH_ENFORCE( m_originalOut >= 0, "Could not dup stdout" ); + CATCH_ENFORCE( m_originalErr >= 0, "Could not dup stderr" ); + } + + std::string getStdout() override { return m_outFile.getContents(); } + std::string getStderr() override { return m_errFile.getContents(); } + void clearBuffers() override { + m_outFile.clear(); + m_errFile.clear(); + } + + void activateImpl() override { + // We flush before starting redirect, to ensure that we do + // not capture the end of message sent before activation. + flushEverything(); + + int ret; + ret = dup2( fileno( m_outFile.getFile() ), fileno( stdout ) ); + CATCH_ENFORCE( ret >= 0, + "dup2 to stdout has failed, errno: " << errno ); + ret = dup2( fileno( m_errFile.getFile() ), fileno( stderr ) ); + CATCH_ENFORCE( ret >= 0, + "dup2 to stderr has failed, errno: " << errno ); + } + void deactivateImpl() override { + // We flush before ending redirect, to ensure that we + // capture all messages sent while the redirect was active. + flushEverything(); + + int ret; + ret = dup2( m_originalOut, fileno( stdout ) ); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stdout has failed, errno: " << errno ); + ret = dup2( m_originalErr, fileno( stderr ) ); + CATCH_ENFORCE( + ret >= 0, + "dup2 of original stderr has failed, errno: " << errno ); + } + }; + +#endif // CATCH_CONFIG_NEW_CAPTURE + + } // end namespace + + bool isRedirectAvailable( OutputRedirect::Kind kind ) { + switch ( kind ) { + // These two are always available + case OutputRedirect::None: + case OutputRedirect::Streams: + return true; +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + case OutputRedirect::FileDescriptors: + return true; +#endif + default: + return false; + } + } + + Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual ) { + if ( actual ) { + // TODO: Clean this up later +#if defined( CATCH_CONFIG_NEW_CAPTURE ) + return Detail::make_unique<FileRedirect>(); +#else + return Detail::make_unique<StreamRedirect>(); +#endif + } else { + return Detail::make_unique<NoopRedirect>(); + } + } + + RedirectGuard scopedActivate( OutputRedirect& redirectImpl ) { + return RedirectGuard( true, redirectImpl ); + } + + RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ) { + return RedirectGuard( false, redirectImpl ); + } + + OutputRedirect::~OutputRedirect() = default; + + RedirectGuard::RedirectGuard( bool activate, OutputRedirect& redirectImpl ): + m_redirect( &redirectImpl ), + m_activate( activate ), + m_previouslyActive( redirectImpl.isActive() ) { + + // Skip cases where there is no actual state change. + if ( m_activate == m_previouslyActive ) { return; } + + if ( m_activate ) { + m_redirect->activate(); + } else { + m_redirect->deactivate(); + } + } + + RedirectGuard::~RedirectGuard() noexcept( false ) { + if ( m_moved ) { return; } + // Skip cases where there is no actual state change. + if ( m_activate == m_previouslyActive ) { return; } + + if ( m_activate ) { + m_redirect->deactivate(); + } else { + m_redirect->activate(); + } + } + + RedirectGuard::RedirectGuard( RedirectGuard&& rhs ) noexcept: + m_redirect( rhs.m_redirect ), + m_activate( rhs.m_activate ), + m_previouslyActive( rhs.m_previouslyActive ), + m_moved( false ) { + rhs.m_moved = true; + } + + RedirectGuard& RedirectGuard::operator=( RedirectGuard&& rhs ) noexcept { + m_redirect = rhs.m_redirect; + m_activate = rhs.m_activate; + m_previouslyActive = rhs.m_previouslyActive; + m_moved = false; + rhs.m_moved = true; + return *this; + } + +} // namespace Catch + +#if defined( CATCH_CONFIG_NEW_CAPTURE ) +# if defined( _MSC_VER ) +# undef dup +# undef dup2 +# undef fileno +# endif +#endif + + + + +#include <limits> +#include <stdexcept> + +namespace Catch { + + Optional<unsigned int> parseUInt(std::string const& input, int base) { + auto trimmed = trim( input ); + // std::stoull is annoying and accepts numbers starting with '-', + // it just negates them into unsigned int + if ( trimmed.empty() || trimmed[0] == '-' ) { + return {}; + } + + CATCH_TRY { + size_t pos = 0; + const auto ret = std::stoull( trimmed, &pos, base ); + + // We did not consume the whole input, so there is an issue + // This can be bunch of different stuff, like multiple numbers + // in the input, or invalid digits/characters and so on. Either + // way, we do not want to return the partially parsed result. + if ( pos != trimmed.size() ) { + return {}; + } + // Too large + if ( ret > std::numeric_limits<unsigned int>::max() ) { + return {}; + } + return static_cast<unsigned int>(ret); + } + CATCH_CATCH_ANON( std::invalid_argument const& ) { + // no conversion could be performed + } + CATCH_CATCH_ANON( std::out_of_range const& ) { + // the input does not fit into an unsigned long long + } + return {}; + } + +} // namespace Catch + + + + +#include <cmath> + +namespace Catch { + +#if !defined(CATCH_CONFIG_POLYFILL_ISNAN) + bool isnan(float f) { + return std::isnan(f); + } + bool isnan(double d) { + return std::isnan(d); + } +#else + // For now we only use this for embarcadero + bool isnan(float f) { + return std::_isnan(f); + } + bool isnan(double d) { + return std::_isnan(d); + } +#endif + +#if !defined( CATCH_CONFIG_GLOBAL_NEXTAFTER ) + float nextafter( float x, float y ) { return std::nextafter( x, y ); } + double nextafter( double x, double y ) { return std::nextafter( x, y ); } +#else + float nextafter( float x, float y ) { return ::nextafterf( x, y ); } + double nextafter( double x, double y ) { return ::nextafter( x, y ); } +#endif + +} // end namespace Catch + + + +namespace Catch { + +namespace { + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4146) // we negate uint32 during the rotate +#endif + // Safe rotr implementation thanks to John Regehr + uint32_t rotate_right(uint32_t val, uint32_t count) { + const uint32_t mask = 31; + count &= mask; + return (val >> count) | (val << (-count & mask)); + } + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +} + + + SimplePcg32::SimplePcg32(result_type seed_) { + seed(seed_); + } + + + void SimplePcg32::seed(result_type seed_) { + m_state = 0; + (*this)(); + m_state += seed_; + (*this)(); + } + + void SimplePcg32::discard(uint64_t skip) { + // We could implement this to run in O(log n) steps, but this + // should suffice for our use case. + for (uint64_t s = 0; s < skip; ++s) { + static_cast<void>((*this)()); + } + } + + SimplePcg32::result_type SimplePcg32::operator()() { + // prepare the output value + const uint32_t xorshifted = static_cast<uint32_t>(((m_state >> 18u) ^ m_state) >> 27u); + const auto output = rotate_right(xorshifted, static_cast<uint32_t>(m_state >> 59u)); + + // advance state + m_state = m_state * 6364136223846793005ULL + s_inc; + + return output; + } + + bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state == rhs.m_state; + } + + bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs) { + return lhs.m_state != rhs.m_state; + } +} + + + + + +#include <ctime> +#include <random> + +namespace Catch { + + std::uint32_t generateRandomSeed( GenerateFrom from ) { + switch ( from ) { + case GenerateFrom::Time: + return static_cast<std::uint32_t>( std::time( nullptr ) ); + + case GenerateFrom::Default: + case GenerateFrom::RandomDevice: { + std::random_device rd; + return Detail::fillBitsFrom<std::uint32_t>( rd ); + } + + default: + CATCH_ERROR("Unknown generation method"); + } + } + +} // end namespace Catch + + + + +namespace Catch { + struct ReporterRegistry::ReporterRegistryImpl { + std::vector<Detail::unique_ptr<EventListenerFactory>> listeners; + std::map<std::string, IReporterFactoryPtr, Detail::CaseInsensitiveLess> + factories; + }; + + ReporterRegistry::ReporterRegistry(): + m_impl( Detail::make_unique<ReporterRegistryImpl>() ) { + // Because it is impossible to move out of initializer list, + // we have to add the elements manually + m_impl->factories["Automake"] = + Detail::make_unique<ReporterFactory<AutomakeReporter>>(); + m_impl->factories["compact"] = + Detail::make_unique<ReporterFactory<CompactReporter>>(); + m_impl->factories["console"] = + Detail::make_unique<ReporterFactory<ConsoleReporter>>(); + m_impl->factories["JUnit"] = + Detail::make_unique<ReporterFactory<JunitReporter>>(); + m_impl->factories["SonarQube"] = + Detail::make_unique<ReporterFactory<SonarQubeReporter>>(); + m_impl->factories["TAP"] = + Detail::make_unique<ReporterFactory<TAPReporter>>(); + m_impl->factories["TeamCity"] = + Detail::make_unique<ReporterFactory<TeamCityReporter>>(); + m_impl->factories["XML"] = + Detail::make_unique<ReporterFactory<XmlReporter>>(); + m_impl->factories["JSON"] = + Detail::make_unique<ReporterFactory<JsonReporter>>(); + } + + ReporterRegistry::~ReporterRegistry() = default; + + IEventListenerPtr + ReporterRegistry::create( std::string const& name, + ReporterConfig&& config ) const { + auto it = m_impl->factories.find( name ); + if ( it == m_impl->factories.end() ) return nullptr; + return it->second->create( CATCH_MOVE( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, + IReporterFactoryPtr factory ) { + CATCH_ENFORCE( name.find( "::" ) == name.npos, + "'::' is not allowed in reporter name: '" + name + + '\'' ); + auto ret = m_impl->factories.emplace( name, CATCH_MOVE( factory ) ); + CATCH_ENFORCE( ret.second, + "reporter using '" + name + + "' as name was already registered" ); + } + void ReporterRegistry::registerListener( + Detail::unique_ptr<EventListenerFactory> factory ) { + m_impl->listeners.push_back( CATCH_MOVE( factory ) ); + } + + std::map<std::string, + IReporterFactoryPtr, + Detail::CaseInsensitiveLess> const& + ReporterRegistry::getFactories() const { + return m_impl->factories; + } + + std::vector<Detail::unique_ptr<EventListenerFactory>> const& + ReporterRegistry::getListeners() const { + return m_impl->listeners; + } +} // namespace Catch + + + + + +#include <algorithm> + +namespace Catch { + + namespace { + struct kvPair { + StringRef key, value; + }; + + kvPair splitKVPair(StringRef kvString) { + auto splitPos = static_cast<size_t>( + std::find( kvString.begin(), kvString.end(), '=' ) - + kvString.begin() ); + + return { kvString.substr( 0, splitPos ), + kvString.substr( splitPos + 1, kvString.size() ) }; + } + } + + namespace Detail { + std::vector<std::string> splitReporterSpec( StringRef reporterSpec ) { + static constexpr auto separator = "::"; + static constexpr size_t separatorSize = 2; + + size_t separatorPos = 0; + auto findNextSeparator = [&reporterSpec]( size_t startPos ) { + static_assert( + separatorSize == 2, + "The code below currently assumes 2 char separator" ); + + auto currentPos = startPos; + do { + while ( currentPos < reporterSpec.size() && + reporterSpec[currentPos] != separator[0] ) { + ++currentPos; + } + if ( currentPos + 1 < reporterSpec.size() && + reporterSpec[currentPos + 1] == separator[1] ) { + return currentPos; + } + ++currentPos; + } while ( currentPos < reporterSpec.size() ); + + return static_cast<size_t>( -1 ); + }; + + std::vector<std::string> parts; + + while ( separatorPos < reporterSpec.size() ) { + const auto nextSeparator = findNextSeparator( separatorPos ); + parts.push_back( static_cast<std::string>( reporterSpec.substr( + separatorPos, nextSeparator - separatorPos ) ) ); + + if ( nextSeparator == static_cast<size_t>( -1 ) ) { + break; + } + separatorPos = nextSeparator + separatorSize; + } + + // Handle a separator at the end. + // This is not a valid spec, but we want to do validation in a + // centralized place + if ( separatorPos == reporterSpec.size() ) { + parts.emplace_back(); + } + + return parts; + } + + Optional<ColourMode> stringToColourMode( StringRef colourMode ) { + if ( colourMode == "default" ) { + return ColourMode::PlatformDefault; + } else if ( colourMode == "ansi" ) { + return ColourMode::ANSI; + } else if ( colourMode == "win32" ) { + return ColourMode::Win32; + } else if ( colourMode == "none" ) { + return ColourMode::None; + } else { + return {}; + } + } + } // namespace Detail + + + bool operator==( ReporterSpec const& lhs, ReporterSpec const& rhs ) { + return lhs.m_name == rhs.m_name && + lhs.m_outputFileName == rhs.m_outputFileName && + lhs.m_colourMode == rhs.m_colourMode && + lhs.m_customOptions == rhs.m_customOptions; + } + + Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ) { + auto parts = Detail::splitReporterSpec( reporterSpec ); + + assert( parts.size() > 0 && "Split should never return empty vector" ); + + std::map<std::string, std::string> kvPairs; + Optional<std::string> outputFileName; + Optional<ColourMode> colourMode; + + // First part is always reporter name, so we skip it + for ( size_t i = 1; i < parts.size(); ++i ) { + auto kv = splitKVPair( parts[i] ); + auto key = kv.key, value = kv.value; + + if ( key.empty() || value.empty() ) { // NOLINT(bugprone-branch-clone) + return {}; + } else if ( key[0] == 'X' ) { + // This is a reporter-specific option, we don't check these + // apart from basic sanity checks + if ( key.size() == 1 ) { + return {}; + } + + auto ret = kvPairs.emplace( std::string(kv.key), std::string(kv.value) ); + if ( !ret.second ) { + // Duplicated key. We might want to handle this differently, + // e.g. by overwriting the existing value? + return {}; + } + } else if ( key == "out" ) { + // Duplicated key + if ( outputFileName ) { + return {}; + } + outputFileName = static_cast<std::string>( value ); + } else if ( key == "colour-mode" ) { + // Duplicated key + if ( colourMode ) { + return {}; + } + colourMode = Detail::stringToColourMode( value ); + // Parsing failed + if ( !colourMode ) { + return {}; + } + } else { + // Unrecognized option + return {}; + } + } + + return ReporterSpec{ CATCH_MOVE( parts[0] ), + CATCH_MOVE( outputFileName ), + CATCH_MOVE( colourMode ), + CATCH_MOVE( kvPairs ) }; + } + +ReporterSpec::ReporterSpec( + std::string name, + Optional<std::string> outputFileName, + Optional<ColourMode> colourMode, + std::map<std::string, std::string> customOptions ): + m_name( CATCH_MOVE( name ) ), + m_outputFileName( CATCH_MOVE( outputFileName ) ), + m_colourMode( CATCH_MOVE( colourMode ) ), + m_customOptions( CATCH_MOVE( customOptions ) ) {} + +} // namespace Catch + + + +#include <cstdio> +#include <sstream> +#include <vector> + +namespace Catch { + + // This class encapsulates the idea of a pool of ostringstreams that can be reused. + struct StringStreams { + std::vector<Detail::unique_ptr<std::ostringstream>> m_streams; + std::vector<std::size_t> m_unused; + std::ostringstream m_referenceStream; // Used for copy state/ flags from + + auto add() -> std::size_t { + if( m_unused.empty() ) { + m_streams.push_back( Detail::make_unique<std::ostringstream>() ); + return m_streams.size()-1; + } + else { + auto index = m_unused.back(); + m_unused.pop_back(); + return index; + } + } + + void release( std::size_t index ) { + m_streams[index]->copyfmt( m_referenceStream ); // Restore initial flags and other state + m_unused.push_back(index); + } + }; + + ReusableStringStream::ReusableStringStream() + : m_index( Singleton<StringStreams>::getMutable().add() ), + m_oss( Singleton<StringStreams>::getMutable().m_streams[m_index].get() ) + {} + + ReusableStringStream::~ReusableStringStream() { + static_cast<std::ostringstream*>( m_oss )->str(""); + m_oss->clear(); + Singleton<StringStreams>::getMutable().release( m_index ); + } + + std::string ReusableStringStream::str() const { + return static_cast<std::ostringstream*>( m_oss )->str(); + } + + void ReusableStringStream::str( std::string const& str ) { + static_cast<std::ostringstream*>( m_oss )->str( str ); + } + + +} + + + + +#include <cassert> +#include <algorithm> + +namespace Catch { + + namespace Generators { + namespace { + struct GeneratorTracker final : TestCaseTracking::TrackerBase, + IGeneratorTracker { + GeneratorBasePtr m_generator; + + GeneratorTracker( + TestCaseTracking::NameAndLocation&& nameAndLocation, + TrackerContext& ctx, + ITracker* parent ): + TrackerBase( CATCH_MOVE( nameAndLocation ), ctx, parent ) {} + + static GeneratorTracker* + acquire( TrackerContext& ctx, + TestCaseTracking::NameAndLocationRef const& + nameAndLocation ) { + GeneratorTracker* tracker; + + ITracker& currentTracker = ctx.currentTracker(); + // Under specific circumstances, the generator we want + // to acquire is also the current tracker. If this is + // the case, we have to avoid looking through current + // tracker's children, and instead return the current + // tracker. + // A case where this check is important is e.g. + // for (int i = 0; i < 5; ++i) { + // int n = GENERATE(1, 2); + // } + // + // without it, the code above creates 5 nested generators. + if ( currentTracker.nameAndLocation() == nameAndLocation ) { + auto thisTracker = currentTracker.parent()->findChild( + nameAndLocation ); + assert( thisTracker ); + assert( thisTracker->isGeneratorTracker() ); + tracker = static_cast<GeneratorTracker*>( thisTracker ); + } else if ( ITracker* childTracker = + currentTracker.findChild( + nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isGeneratorTracker() ); + tracker = + static_cast<GeneratorTracker*>( childTracker ); + } else { + return nullptr; + } + + if ( !tracker->isComplete() ) { tracker->open(); } + + return tracker; + } + + // TrackerBase interface + bool isGeneratorTracker() const override { return true; } + auto hasGenerator() const -> bool override { + return !!m_generator; + } + void close() override { + TrackerBase::close(); + // If a generator has a child (it is followed by a section) + // and none of its children have started, then we must wait + // until later to start consuming its values. + // This catches cases where `GENERATE` is placed between two + // `SECTION`s. + // **The check for m_children.empty cannot be removed**. + // doing so would break `GENERATE` _not_ followed by + // `SECTION`s. + const bool should_wait_for_child = [&]() { + // No children -> nobody to wait for + if ( m_children.empty() ) { return false; } + // If at least one child started executing, don't wait + if ( std::find_if( + m_children.begin(), + m_children.end(), + []( TestCaseTracking::ITrackerPtr const& + tracker ) { + return tracker->hasStarted(); + } ) != m_children.end() ) { + return false; + } + + // No children have started. We need to check if they + // _can_ start, and thus we should wait for them, or + // they cannot start (due to filters), and we shouldn't + // wait for them + ITracker* parent = m_parent; + // This is safe: there is always at least one section + // tracker in a test case tracking tree + while ( !parent->isSectionTracker() ) { + parent = parent->parent(); + } + assert( parent && + "Missing root (test case) level section" ); + + auto const& parentSection = + static_cast<SectionTracker const&>( *parent ); + auto const& filters = parentSection.getFilters(); + // No filters -> no restrictions on running sections + if ( filters.empty() ) { return true; } + + for ( auto const& child : m_children ) { + if ( child->isSectionTracker() && + std::find( filters.begin(), + filters.end(), + static_cast<SectionTracker const&>( + *child ) + .trimmedName() ) != + filters.end() ) { + return true; + } + } + return false; + }(); + + // This check is a bit tricky, because m_generator->next() + // has a side-effect, where it consumes generator's current + // value, but we do not want to invoke the side-effect if + // this generator is still waiting for any child to start. + assert( m_generator && "Tracker without generator" ); + if ( should_wait_for_child || + ( m_runState == CompletedSuccessfully && + m_generator->countedNext() ) ) { + m_children.clear(); + m_runState = Executing; + } + } + + // IGeneratorTracker interface + auto getGenerator() const -> GeneratorBasePtr const& override { + return m_generator; + } + void setGenerator( GeneratorBasePtr&& generator ) override { + m_generator = CATCH_MOVE( generator ); + } + }; + } // namespace + } + + RunContext::RunContext(IConfig const* _config, IEventListenerPtr&& reporter) + : m_runInfo(_config->name()), + m_config(_config), + m_reporter(CATCH_MOVE(reporter)), + m_lastAssertionInfo{ StringRef(), SourceLineInfo("",0), StringRef(), ResultDisposition::Normal }, + m_outputRedirect( makeOutputRedirect( m_reporter->getPreferences().shouldRedirectStdOut ) ), + m_includeSuccessfulResults( m_config->includeSuccessfulResults() || m_reporter->getPreferences().shouldReportAllAssertions ) + { + getCurrentMutableContext().setResultCapture( this ); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + Totals RunContext::runTest(TestCaseHandle const& testCase) { + const Totals prevTotals = m_totals; + + auto const& testInfo = testCase.getTestCaseInfo(); + m_reporter->testCaseStarting(testInfo); + testCase.prepareTestCase(); + m_activeTestCase = &testCase; + + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + + // We intentionally only seed the internal RNG once per test case, + // before it is first invoked. The reason for that is a complex + // interplay of generator/section implementation details and the + // Random*Generator types. + // + // The issue boils down to us needing to seed the Random*Generators + // with different seed each, so that they return different sequences + // of random numbers. We do this by giving them a number from the + // shared RNG instance as their seed. + // + // However, this runs into an issue if the reseeding happens each + // time the test case is entered (as opposed to first time only), + // because multiple generators could get the same seed, e.g. in + // ```cpp + // TEST_CASE() { + // auto i = GENERATE(take(10, random(0, 100)); + // SECTION("A") { + // auto j = GENERATE(take(10, random(0, 100)); + // } + // SECTION("B") { + // auto k = GENERATE(take(10, random(0, 100)); + // } + // } + // ``` + // `i` and `j` would properly return values from different sequences, + // but `i` and `k` would return the same sequence, because their seed + // would be the same. + // (The reason their seeds would be the same is that the generator + // for k would be initialized when the test case is entered the second + // time, after the shared RNG instance was reset to the same value + // it had when the generator for i was initialized.) + seedRng( *m_config ); + + uint64_t testRuns = 0; + std::string redirectedCout; + std::string redirectedCerr; + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocationRef(testInfo.name, testInfo.lineInfo)); + + m_reporter->testCasePartialStarting(testInfo, testRuns); + + const auto beforeRunTotals = m_totals; + runCurrentTest(); + std::string oneRunCout = m_outputRedirect->getStdout(); + std::string oneRunCerr = m_outputRedirect->getStderr(); + m_outputRedirect->clearBuffers(); + redirectedCout += oneRunCout; + redirectedCerr += oneRunCerr; + + const auto singleRunTotals = m_totals.delta(beforeRunTotals); + auto statsForOneRun = TestCaseStats(testInfo, singleRunTotals, CATCH_MOVE(oneRunCout), CATCH_MOVE(oneRunCerr), aborting()); + m_reporter->testCasePartialEnded(statsForOneRun, testRuns); + + ++testRuns; + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + testCase.tearDownTestCase(); + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + CATCH_MOVE(redirectedCout), + CATCH_MOVE(redirectedCerr), + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + + void RunContext::assertionEnded(AssertionResult&& result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + m_lastAssertionPassed = true; + } else if (result.getResultType() == ResultWas::ExplicitSkip) { + m_totals.assertions.skipped++; + m_lastAssertionPassed = true; + } else if (!result.succeeded()) { + m_lastAssertionPassed = false; + if (result.isOk()) { + } + else if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + else { + m_lastAssertionPassed = true; + } + + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ); + } + + if ( result.getResultType() != ResultWas::Warning ) { + m_messageScopes.clear(); + } + + // Reset working state. assertion info will be reset after + // populateReaction is run if it is needed + m_lastResult = CATCH_MOVE( result ); + } + void RunContext::resetAssertionInfo() { + m_lastAssertionInfo.macroName = StringRef(); + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"_sr; + m_lastAssertionInfo.resultDisposition = ResultDisposition::Normal; + } + + void RunContext::notifyAssertionStarted( AssertionInfo const& info ) { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->assertionStarting( info ); + } + + bool RunContext::sectionStarted( StringRef sectionName, + SourceLineInfo const& sectionLineInfo, + Counts& assertions ) { + ITracker& sectionTracker = + SectionTracker::acquire( m_trackerContext, + TestCaseTracking::NameAndLocationRef( + sectionName, sectionLineInfo ) ); + + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + SectionInfo sectionInfo( sectionLineInfo, static_cast<std::string>(sectionName) ); + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->sectionStarting( sectionInfo ); + } + + assertions = m_totals.assertions; + + return true; + } + IGeneratorTracker* + RunContext::acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ) { + using namespace Generators; + GeneratorTracker* tracker = GeneratorTracker::acquire( + m_trackerContext, + TestCaseTracking::NameAndLocationRef( + generatorName, lineInfo ) ); + m_lastAssertionInfo.lineInfo = lineInfo; + return tracker; + } + + IGeneratorTracker* RunContext::createGeneratorTracker( + StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr&& generator ) { + + auto nameAndLoc = TestCaseTracking::NameAndLocation( static_cast<std::string>( generatorName ), lineInfo ); + auto& currentTracker = m_trackerContext.currentTracker(); + assert( + currentTracker.nameAndLocation() != nameAndLoc && + "Trying to create tracker for a genreator that already has one" ); + + auto newTracker = Catch::Detail::make_unique<Generators::GeneratorTracker>( + CATCH_MOVE(nameAndLoc), m_trackerContext, ¤tTracker ); + auto ret = newTracker.get(); + currentTracker.addChild( CATCH_MOVE( newTracker ) ); + + ret->setGenerator( CATCH_MOVE( generator ) ); + ret->open(); + return ret; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo&& endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->sectionEnded( + SectionStats( CATCH_MOVE( endInfo.sectionInfo ), + assertions, + endInfo.durationInSeconds, + missingAssertions ) ); + } + + m_messages.clear(); + m_messageScopes.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo&& endInfo) { + if ( m_unfinishedSections.empty() ) { + m_activeSections.back()->fail(); + } else { + m_activeSections.back()->close(); + } + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(CATCH_MOVE(endInfo)); + } + + void RunContext::benchmarkPreparing( StringRef name ) { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->benchmarkPreparing( name ); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats<> const& stats ) { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->benchmarkEnded( stats ); + } + void RunContext::benchmarkFailed( StringRef error ) { + auto _ = scopedDeactivate( *m_outputRedirect ); + m_reporter->benchmarkFailed( error ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + void RunContext::emplaceUnscopedMessage( MessageBuilder&& builder ) { + m_messageScopes.emplace_back( CATCH_MOVE(builder) ); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // TODO: scoped deactivate here? Just give up and do best effort? + // the deactivation can break things further, OTOH so can the + // capture + auto _ = scopedDeactivate( *m_outputRedirect ); + + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered( message ); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = static_cast<std::string>(message); + AssertionResult result(m_lastAssertionInfo, CATCH_MOVE(tempResult)); + + assertionEnded(CATCH_MOVE(result) ); + resetAssertionInfo(); + + // Best effort cleanup for sections that have not been destructed yet + // Since this is a fatal error, we have not had and won't have the opportunity to destruct them properly + while (!m_activeSections.empty()) { + auto nl = m_activeSections.back()->nameAndLocation(); + SectionEndInfo endInfo{ SectionInfo(CATCH_MOVE(nl.location), CATCH_MOVE(nl.name)), {}, 0.0 }; + sectionEndedEarly(CATCH_MOVE(endInfo)); + } + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, 0, false); + m_reporter->sectionEnded( testCaseSectionStats ); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_lastAssertionPassed; + } + + void RunContext::assertionPassed() { + m_lastAssertionPassed = true; + ++m_totals.assertions.passed; + resetAssertionInfo(); + m_messageScopes.clear(); + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed >= static_cast<std::size_t>(m_config->abortAfter()); + } + + void RunContext::runCurrentTest() { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + m_lastAssertionInfo = { "TEST_CASE"_sr, testCaseInfo.lineInfo, StringRef(), ResultDisposition::Normal }; + + Timer timer; + CATCH_TRY { + { + auto _ = scopedActivate( *m_outputRedirect ); + timer.start(); + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } CATCH_CATCH_ANON (TestFailureException&) { + // This just means the test was aborted due to failure + } CATCH_CATCH_ANON (TestSkipException&) { + // This just means the test was explicitly skipped + } CATCH_CATCH_ALL { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if( m_shouldReportUnexpected ) { + AssertionReaction dummyReaction; + handleUnexpectedInflightException( m_lastAssertionInfo, translateActiveException(), dummyReaction ); + } + } + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + m_messageScopes.clear(); + + SectionStats testCaseSectionStats(CATCH_MOVE(testCaseSection), assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + // We need to engage a handler for signals/structured exceptions + // before running the tests themselves, or the binary can crash + // without failed test being reported. + FatalConditionHandlerGuard _(&m_fatalConditionhandler); + // We keep having issue where some compilers warn about an unused + // variable, even though the type has non-trivial constructor and + // destructor. This is annoying and ugly, but it makes them stfu. + (void)_; + + m_activeTestCase->invoke(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for ( auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) { + sectionEnded( CATCH_MOVE( *it ) ); + } + m_unfinishedSections.clear(); + } + + void RunContext::handleExpr( + AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction + ) { + bool negated = isFalseTest( info.resultDisposition ); + bool result = expr.getResult() != negated; + + if( result ) { + if (!m_includeSuccessfulResults) { + assertionPassed(); + } + else { + reportExpr(info, ResultWas::Ok, &expr, negated); + } + } + else { + reportExpr(info, ResultWas::ExpressionFailed, &expr, negated ); + populateReaction( reaction ); + } + resetAssertionInfo(); + } + void RunContext::reportExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ) { + + m_lastAssertionInfo = info; + AssertionResultData data( resultType, LazyExpression( negated ) ); + + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; + + assertionEnded( CATCH_MOVE(assertionResult) ); + } + + void RunContext::handleMessage( + AssertionInfo const& info, + ResultWas::OfType resultType, + std::string&& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = CATCH_MOVE( message ); + AssertionResult assertionResult{ m_lastAssertionInfo, + CATCH_MOVE( data ) }; + + const auto isOk = assertionResult.isOk(); + assertionEnded( CATCH_MOVE(assertionResult) ); + if ( !isOk ) { + populateReaction( reaction ); + } else if ( resultType == ResultWas::ExplicitSkip ) { + // TODO: Need to handle this explicitly, as ExplicitSkip is + // considered "OK" + reaction.shouldSkip = true; + } + resetAssertionInfo(); + } + void RunContext::handleUnexpectedExceptionNotThrown( + AssertionInfo const& info, + AssertionReaction& reaction + ) { + handleNonExpr(info, Catch::ResultWas::DidntThrowException, reaction); + } + + void RunContext::handleUnexpectedInflightException( + AssertionInfo const& info, + std::string&& message, + AssertionReaction& reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = CATCH_MOVE(message); + AssertionResult assertionResult{ info, CATCH_MOVE(data) }; + assertionEnded( CATCH_MOVE(assertionResult) ); + populateReaction( reaction ); + resetAssertionInfo(); + } + + void RunContext::populateReaction( AssertionReaction& reaction ) { + reaction.shouldDebugBreak = m_config->shouldDebugBreak(); + reaction.shouldThrow = aborting() || (m_lastAssertionInfo.resultDisposition & ResultDisposition::Normal); + } + + void RunContext::handleIncomplete( + AssertionInfo const& info + ) { + using namespace std::string_literals; + m_lastAssertionInfo = info; + + AssertionResultData data( ResultWas::ThrewException, LazyExpression( false ) ); + data.message = "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE"s; + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; + assertionEnded( CATCH_MOVE(assertionResult) ); + resetAssertionInfo(); + } + void RunContext::handleNonExpr( + AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction + ) { + m_lastAssertionInfo = info; + + AssertionResultData data( resultType, LazyExpression( false ) ); + AssertionResult assertionResult{ info, CATCH_MOVE( data ) }; + + const auto isOk = assertionResult.isOk(); + assertionEnded( CATCH_MOVE(assertionResult) ); + if ( !isOk ) { populateReaction( reaction ); } + resetAssertionInfo(); + } + + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } + + void seedRng(IConfig const& config) { + sharedRng().seed(config.rngSeed()); + } + + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + +} + + + +namespace Catch { + + Section::Section( SectionInfo&& info ): + m_info( CATCH_MOVE( info ) ), + m_sectionIncluded( + getResultCapture().sectionStarted( m_info.name, m_info.lineInfo, m_assertions ) ) { + // Non-"included" sections will not use the timing information + // anyway, so don't bother with the potential syscall. + if (m_sectionIncluded) { + m_timer.start(); + } + } + + Section::Section( SourceLineInfo const& _lineInfo, + StringRef _name, + const char* const ): + m_info( { "invalid", static_cast<std::size_t>( -1 ) }, std::string{} ), + m_sectionIncluded( + getResultCapture().sectionStarted( _name, _lineInfo, m_assertions ) ) { + // We delay initialization the SectionInfo member until we know + // this section needs it, so we avoid allocating std::string for name. + // We also delay timer start to avoid the potential syscall unless we + // will actually use the result. + if ( m_sectionIncluded ) { + m_info.name = static_cast<std::string>( _name ); + m_info.lineInfo = _lineInfo; + m_timer.start(); + } + } + + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo{ CATCH_MOVE(m_info), m_assertions, m_timer.getElapsedSeconds() }; + if ( uncaught_exceptions() ) { + getResultCapture().sectionEndedEarly( CATCH_MOVE(endInfo) ); + } else { + getResultCapture().sectionEnded( CATCH_MOVE( endInfo ) ); + } + } + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + + +} // end namespace Catch + + + +#include <vector> + +namespace Catch { + + namespace { + static auto getSingletons() -> std::vector<ISingleton*>*& { + static std::vector<ISingleton*>* g_singletons = nullptr; + if( !g_singletons ) + g_singletons = new std::vector<ISingleton*>(); + return g_singletons; + } + } + + ISingleton::~ISingleton() = default; + + void addSingleton(ISingleton* singleton ) { + getSingletons()->push_back( singleton ); + } + void cleanupSingletons() { + auto& singletons = getSingletons(); + for( auto singleton : *singletons ) + delete singleton; + delete singletons; + singletons = nullptr; + } + +} // namespace Catch + + + +#include <cstring> +#include <ostream> + +namespace Catch { + + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + // We can assume that the same file will usually have the same pointer. + // Thus, if the pointers are the same, there is no point in calling the strcmp + return line < other.line || ( line == other.line && file != other.file && (std::strcmp(file, other.file) < 0)); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; + } + +} // end namespace Catch + + + + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + CATCH_TRY { + m_exceptions.push_back(exception); + } CATCH_CATCH_ALL { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } +#endif + +} // end namespace Catch + + + + + +#include <iostream> + +namespace Catch { + +// If you #define this you must implement these functions +#if !defined( CATCH_CONFIG_NOSTDOUT ) + std::ostream& cout() { return std::cout; } + std::ostream& cerr() { return std::cerr; } + std::ostream& clog() { return std::clog; } +#endif + +} // namespace Catch + + + +#include <ostream> +#include <cstring> +#include <cctype> +#include <vector> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( StringRef s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + for ( char& c : s ) { + c = toLower( c ); + } + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + char toLower(char c) { + return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); + } + + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + StringRef trim(StringRef ref) { + const auto is_ws = [](char c) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + }; + size_t real_begin = 0; + while (real_begin < ref.size() && is_ws(ref[real_begin])) { ++real_begin; } + size_t real_end = ref.size(); + while (real_end > real_begin && is_ws(ref[real_end - 1])) { --real_end; } + + return ref.substr(real_begin, real_end - real_begin); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + std::size_t i = str.find( replaceThis ); + if (i == std::string::npos) { + return false; + } + std::size_t copyBegin = 0; + std::string origStr = CATCH_MOVE(str); + str.clear(); + // There is at least one replacement, so reserve with the best guess + // we can make without actually counting the number of occurences. + str.reserve(origStr.size() - replaceThis.size() + withThis.size()); + do { + str.append(origStr, copyBegin, i-copyBegin ); + str += withThis; + copyBegin = i + replaceThis.size(); + if( copyBegin < origStr.size() ) + i = origStr.find( replaceThis, copyBegin ); + else + i = std::string::npos; + } while( i != std::string::npos ); + if ( copyBegin < origStr.size() ) { + str.append(origStr, copyBegin, origStr.size() ); + } + return true; + } + + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ) { + std::vector<StringRef> subStrings; + std::size_t start = 0; + for(std::size_t pos = 0; pos < str.size(); ++pos ) { + if( str[pos] == delimiter ) { + if( pos - start > 1 ) + subStrings.push_back( str.substr( start, pos-start ) ); + start = pos+1; + } + } + if( start < str.size() ) + subStrings.push_back( str.substr( start, str.size()-start ) ); + return subStrings; + } + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} + + + +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cstdint> + +namespace Catch { + StringRef::StringRef( char const* rawChars ) noexcept + : StringRef( rawChars, std::strlen(rawChars) ) + {} + + + bool StringRef::operator<(StringRef rhs) const noexcept { + if (m_size < rhs.m_size) { + return strncmp(m_start, rhs.m_start, m_size) <= 0; + } + return strncmp(m_start, rhs.m_start, rhs.m_size) < 0; + } + + int StringRef::compare( StringRef rhs ) const { + auto cmpResult = + strncmp( m_start, rhs.m_start, std::min( m_size, rhs.m_size ) ); + + // This means that strncmp found a difference before the strings + // ended, and we can return it directly + if ( cmpResult != 0 ) { + return cmpResult; + } + + // If strings are equal up to length, then their comparison results on + // their size + if ( m_size < rhs.m_size ) { + return -1; + } else if ( m_size > rhs.m_size ) { + return 1; + } else { + return 0; + } + } + + auto operator << ( std::ostream& os, StringRef str ) -> std::ostream& { + return os.write(str.data(), static_cast<std::streamsize>(str.size())); + } + + std::string operator+(StringRef lhs, StringRef rhs) { + std::string ret; + ret.reserve(lhs.size() + rhs.size()); + ret += lhs; + ret += rhs; + return ret; + } + + auto operator+=( std::string& lhs, StringRef rhs ) -> std::string& { + lhs.append(rhs.data(), rhs.size()); + return lhs; + } + +} // namespace Catch + + + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() = default; + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() = default; + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch + + + + +namespace Catch { + TestCaseInfoHasher::TestCaseInfoHasher( hash_t seed ): m_seed( seed ) {} + + uint32_t TestCaseInfoHasher::operator()( TestCaseInfo const& t ) const { + // FNV-1a hash algorithm that is designed for uniqueness: + const hash_t prime = 1099511628211u; + hash_t hash = 14695981039346656037u; + for ( const char c : t.name ) { + hash ^= c; + hash *= prime; + } + for ( const char c : t.className ) { + hash ^= c; + hash *= prime; + } + for ( const Tag& tag : t.tags ) { + for ( const char c : tag.original ) { + hash ^= c; + hash *= prime; + } + } + hash ^= m_seed; + hash *= prime; + const uint32_t low{ static_cast<uint32_t>( hash ) }; + const uint32_t high{ static_cast<uint32_t>( hash >> 32 ) }; + return low * high; + } +} // namespace Catch + + + + +#include <algorithm> +#include <set> + +namespace Catch { + + namespace { + static void enforceNoDuplicateTestCases( + std::vector<TestCaseHandle> const& tests ) { + auto testInfoCmp = []( TestCaseInfo const* lhs, + TestCaseInfo const* rhs ) { + return *lhs < *rhs; + }; + std::set<TestCaseInfo const*, decltype( testInfoCmp )&> seenTests( + testInfoCmp ); + for ( auto const& test : tests ) { + const auto infoPtr = &test.getTestCaseInfo(); + const auto prev = seenTests.insert( infoPtr ); + CATCH_ENFORCE( prev.second, + "error: test case \"" + << infoPtr->name << "\", with tags \"" + << infoPtr->tagsAsString() + << "\" already defined.\n" + << "\tFirst seen at " + << ( *prev.first )->lineInfo << "\n" + << "\tRedefined at " << infoPtr->lineInfo ); + } + } + + static bool matchTest( TestCaseHandle const& testCase, + TestSpec const& testSpec, + IConfig const& config ) { + return testSpec.matches( testCase.getTestCaseInfo() ) && + isThrowSafe( testCase, config ); + } + + } // end unnamed namespace + + std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ) { + switch (config.runOrder()) { + case TestRunOrder::Declared: + return unsortedTestCases; + + case TestRunOrder::LexicographicallySorted: { + std::vector<TestCaseHandle> sorted = unsortedTestCases; + std::sort( + sorted.begin(), + sorted.end(), + []( TestCaseHandle const& lhs, TestCaseHandle const& rhs ) { + return lhs.getTestCaseInfo() < rhs.getTestCaseInfo(); + } + ); + return sorted; + } + case TestRunOrder::Randomized: { + using TestWithHash = std::pair<TestCaseInfoHasher::hash_t, TestCaseHandle>; + + TestCaseInfoHasher h{ config.rngSeed() }; + std::vector<TestWithHash> indexed_tests; + indexed_tests.reserve(unsortedTestCases.size()); + + for (auto const& handle : unsortedTestCases) { + indexed_tests.emplace_back(h(handle.getTestCaseInfo()), handle); + } + + std::sort( indexed_tests.begin(), + indexed_tests.end(), + []( TestWithHash const& lhs, TestWithHash const& rhs ) { + if ( lhs.first == rhs.first ) { + return lhs.second.getTestCaseInfo() < + rhs.second.getTestCaseInfo(); + } + return lhs.first < rhs.first; + } ); + + std::vector<TestCaseHandle> randomized; + randomized.reserve(indexed_tests.size()); + + for (auto const& indexed : indexed_tests) { + randomized.push_back(indexed.second); + } + + return randomized; + } + } + + CATCH_INTERNAL_ERROR("Unknown test order value!"); + } + + bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ) { + return !testCase.getTestCaseInfo().throws() || config.allowThrows(); + } + + std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCaseHandle> filtered; + filtered.reserve( testCases.size() ); + for (auto const& testCase : testCases) { + if ((!testSpec.hasFilters() && !testCase.getTestCaseInfo().isHidden()) || + (testSpec.hasFilters() && matchTest(testCase, testSpec, config))) { + filtered.push_back(testCase); + } + } + return createShard(filtered, config.shardCount(), config.shardIndex()); + } + std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + TestRegistry::~TestRegistry() = default; + + void TestRegistry::registerTest(Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker) { + m_handles.emplace_back(testInfo.get(), testInvoker.get()); + m_viewed_test_infos.push_back(testInfo.get()); + m_owned_test_infos.push_back(CATCH_MOVE(testInfo)); + m_invokers.push_back(CATCH_MOVE(testInvoker)); + } + + std::vector<TestCaseInfo*> const& TestRegistry::getAllInfos() const { + return m_viewed_test_infos; + } + + std::vector<TestCaseHandle> const& TestRegistry::getAllTests() const { + return m_handles; + } + std::vector<TestCaseHandle> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_handles ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_handles ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + +} // end namespace Catch + + + + +#include <algorithm> +#include <cassert> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string&& _name, SourceLineInfo const& _location ) + : name( CATCH_MOVE(_name) ), + location( _location ) + {} + + + ITracker::~ITracker() = default; + + void ITracker::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void ITracker::addChild( ITrackerPtr&& child ) { + m_children.push_back( CATCH_MOVE(child) ); + } + + ITracker* ITracker::findChild( NameAndLocationRef const& nameAndLocation ) { + auto it = std::find_if( + m_children.begin(), + m_children.end(), + [&nameAndLocation]( ITrackerPtr const& tracker ) { + auto const& tnameAndLoc = tracker->nameAndLocation(); + if ( tnameAndLoc.location.line != + nameAndLocation.location.line ) { + return false; + } + return tnameAndLoc == nameAndLocation; + } ); + return ( it != m_children.end() ) ? it->get() : nullptr; + } + + bool ITracker::isSectionTracker() const { return false; } + bool ITracker::isGeneratorTracker() const { return false; } + + bool ITracker::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + + bool ITracker::hasStarted() const { return m_runState != NotStarted; } + + void ITracker::openChild() { + if (m_runState != ExecutingChildren) { + m_runState = ExecutingChildren; + if (m_parent) { + m_parent->openChild(); + } + } + } + + ITracker& TrackerContext::startRun() { + using namespace std::string_literals; + m_rootTracker = Catch::Detail::make_unique<SectionTracker>( + NameAndLocation( "{root}"s, CATCH_INTERNAL_LINEINFO ), + *this, + nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + + TrackerBase::TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ): + ITracker(CATCH_MOVE(nameAndLocation), parent), + m_ctx( ctx ) + {} + + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( std::all_of(m_children.begin(), m_children.end(), [](ITrackerPtr const& t){ return t->isComplete(); }) ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( CATCH_MOVE(nameAndLocation), ctx, parent ), + m_trimmed_name(trim(StringRef(ITracker::nameAndLocation().name))) + { + if( parent ) { + while ( !parent->isSectionTracker() ) { + parent = parent->parent(); + } + + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isComplete() const { + bool complete = true; + + if (m_filters.empty() + || m_filters[0].empty() + || std::find(m_filters.begin(), m_filters.end(), m_trimmed_name) != m_filters.end()) { + complete = TrackerBase::isComplete(); + } + return complete; + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation ) { + SectionTracker* tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if ( ITracker* childTracker = + currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + tracker = static_cast<SectionTracker*>( childTracker ); + } else { + auto newTracker = Catch::Detail::make_unique<SectionTracker>( + NameAndLocation{ static_cast<std::string>(nameAndLocation.name), + nameAndLocation.location }, + ctx, + ¤tTracker ); + tracker = newTracker.get(); + currentTracker.addChild( CATCH_MOVE( newTracker ) ); + } + + if ( !ctx.completedCycle() ) { + tracker->tryOpen(); + } + + return *tracker; + } + + void SectionTracker::tryOpen() { + if( !isComplete() ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.reserve( m_filters.size() + filters.size() + 2 ); + m_filters.emplace_back(StringRef{}); // Root - should never be consulted + m_filters.emplace_back(StringRef{}); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector<StringRef> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), filters.begin()+1, filters.end() ); + } + + StringRef SectionTracker::trimmedName() const { + return m_trimmed_name; + } + +} // namespace TestCaseTracking + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + + +namespace Catch { + + void throw_test_failure_exception() { +#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) + throw TestFailureException{}; +#else + CATCH_ERROR( "Test failure requires aborting test!" ); +#endif + } + + void throw_test_skip_exception() { +#if !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) + throw Catch::TestSkipException(); +#else + CATCH_ERROR( "Explicitly skipping tests during runtime requires exceptions" ); +#endif + } + +} // namespace Catch + + + +#include <algorithm> +#include <iterator> + +namespace Catch { + void ITestInvoker::prepareTestCase() {} + void ITestInvoker::tearDownTestCase() {} + ITestInvoker::~ITestInvoker() = default; + + namespace { + static StringRef extractClassName( StringRef classOrMethodName ) { + if ( !startsWith( classOrMethodName, '&' ) ) { + return classOrMethodName; + } + + // Remove the leading '&' to avoid having to special case it later + const auto methodName = + classOrMethodName.substr( 1, classOrMethodName.size() ); + + auto reverseStart = std::make_reverse_iterator( methodName.end() ); + auto reverseEnd = std::make_reverse_iterator( methodName.begin() ); + + // We make a simplifying assumption that ":" is only present + // in the input as part of "::" from C++ typenames (this is + // relatively safe assumption because the input is generated + // as stringification of type through preprocessor). + auto lastColons = std::find( reverseStart, reverseEnd, ':' ) + 1; + auto secondLastColons = + std::find( lastColons + 1, reverseEnd, ':' ); + + auto const startIdx = reverseEnd - secondLastColons; + auto const classNameSize = secondLastColons - lastColons - 1; + + return methodName.substr( + static_cast<std::size_t>( startIdx ), + static_cast<std::size_t>( classNameSize ) ); + } + + class TestInvokerAsFunction final : public ITestInvoker { + using TestType = void ( * )(); + TestType m_testAsFunction; + + public: + constexpr TestInvokerAsFunction( TestType testAsFunction ) noexcept: + m_testAsFunction( testAsFunction ) {} + + void invoke() const override { m_testAsFunction(); } + }; + + } // namespace + + Detail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ) { + return Detail::make_unique<TestInvokerAsFunction>( testAsFunction ); + } + + AutoReg::AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { + CATCH_TRY { + getMutableRegistryHub() + .registerTest( + makeTestCaseInfo( + extractClassName( classOrMethod ), + nameAndTags, + lineInfo), + CATCH_MOVE(invoker) + ); + } CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } +} + + + + + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + m_substring.reserve(m_arg.size()); + m_patternName.reserve(m_arg.size()); + m_realPatternPos = 0; + + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + //if visitChar fails + if( !visitChar( m_arg[m_pos] ) ){ + m_testSpec.m_invalidSpecs.push_back(arg); + break; + } + endMode(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return CATCH_MOVE(m_testSpec); + } + bool TestSpecParser::visitChar( char c ) { + if( (m_mode != EscapedName) && (c == '\\') ) { + escape(); + addCharToPattern(c); + return true; + }else if((m_mode != EscapedName) && (c == ',') ) { + return separate(); + } + + switch( m_mode ) { + case None: + if( processNoneChar( c ) ) + return true; + break; + case Name: + processNameChar( c ); + break; + case EscapedName: + endMode(); + addCharToPattern(c); + return true; + default: + case Tag: + case QuotedName: + if( processOtherChar( c ) ) + return true; + break; + } + + m_substring += c; + if( !isControlChar( c ) ) { + m_patternName += c; + m_realPatternPos++; + } + return true; + } + // Two of the processing methods return true to signal the caller to return + // without adding the given character to the current pattern strings + bool TestSpecParser::processNoneChar( char c ) { + switch( c ) { + case ' ': + return true; + case '~': + m_exclusion = true; + return false; + case '[': + startNewMode( Tag ); + return false; + case '"': + startNewMode( QuotedName ); + return false; + default: + startNewMode( Name ); + return false; + } + } + void TestSpecParser::processNameChar( char c ) { + if( c == '[' ) { + if( m_substring == "exclude:" ) + m_exclusion = true; + else + endMode(); + startNewMode( Tag ); + } + } + bool TestSpecParser::processOtherChar( char c ) { + if( !isControlChar( c ) ) + return false; + m_substring += c; + endMode(); + return true; + } + void TestSpecParser::startNewMode( Mode mode ) { + m_mode = mode; + } + void TestSpecParser::endMode() { + switch( m_mode ) { + case Name: + case QuotedName: + return addNamePattern(); + case Tag: + return addTagPattern(); + case EscapedName: + revertBackToLastMode(); + return; + case None: + default: + return startNewMode( None ); + } + } + void TestSpecParser::escape() { + saveLastMode(); + m_mode = EscapedName; + m_escapeChars.push_back(m_realPatternPos); + } + bool TestSpecParser::isControlChar( char c ) const { + switch( m_mode ) { + default: + return false; + case None: + return c == '~'; + case Name: + return c == '['; + case EscapedName: + return true; + case QuotedName: + return c == '"'; + case Tag: + return c == '[' || c == ']'; + } + } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_required.empty() || !m_currentFilter.m_forbidden.empty() ) { + m_testSpec.m_filters.push_back( CATCH_MOVE(m_currentFilter) ); + m_currentFilter = TestSpec::Filter(); + } + } + + void TestSpecParser::saveLastMode() { + lastMode = m_mode; + } + + void TestSpecParser::revertBackToLastMode() { + m_mode = lastMode; + } + + bool TestSpecParser::separate() { + if( (m_mode==QuotedName) || (m_mode==Tag) ){ + //invalid argument, signal failure to previous scope. + m_mode = None; + m_pos = m_arg.size(); + m_substring.clear(); + m_patternName.clear(); + m_realPatternPos = 0; + return false; + } + endMode(); + addFilter(); + return true; //success + } + + std::string TestSpecParser::preprocessPattern() { + std::string token = m_patternName; + for (std::size_t i = 0; i < m_escapeChars.size(); ++i) + token = token.substr(0, m_escapeChars[i] - i) + token.substr(m_escapeChars[i] - i + 1); + m_escapeChars.clear(); + if (startsWith(token, "exclude:")) { + m_exclusion = true; + token = token.substr(8); + } + + m_patternName.clear(); + m_realPatternPos = 0; + + return token; + } + + void TestSpecParser::addNamePattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::NamePattern>(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::NamePattern>(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + + void TestSpecParser::addTagPattern() { + auto token = preprocessPattern(); + + if (!token.empty()) { + // If the tag pattern is the "hide and tag" shorthand (e.g. [.foo]) + // we have to create a separate hide tag and shorten the real one + if (token.size() > 1 && token[0] == '.') { + token.erase(token.begin()); + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::TagPattern>(".", m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::TagPattern>(".", m_substring)); + } + } + if (m_exclusion) { + m_currentFilter.m_forbidden.emplace_back(Detail::make_unique<TestSpec::TagPattern>(token, m_substring)); + } else { + m_currentFilter.m_required.emplace_back(Detail::make_unique<TestSpec::TagPattern>(token, m_substring)); + } + } + m_substring.clear(); + m_exclusion = false; + m_mode = None; + } + +} // namespace Catch + + + +#include <algorithm> +#include <cstring> +#include <ostream> + +namespace { + bool isWhitespace( char c ) { + return c == ' ' || c == '\t' || c == '\n' || c == '\r'; + } + + bool isBreakableBefore( char c ) { + static const char chars[] = "[({<|"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + + bool isBreakableAfter( char c ) { + static const char chars[] = "])}>.,:;*+-=&/\\"; + return std::memchr( chars, c, sizeof( chars ) - 1 ) != nullptr; + } + +} // namespace + +namespace Catch { + namespace TextFlow { + void AnsiSkippingString::preprocessString() { + for ( auto it = m_string.begin(); it != m_string.end(); ) { + // try to read through an ansi sequence + while ( it != m_string.end() && *it == '\033' && + it + 1 != m_string.end() && *( it + 1 ) == '[' ) { + auto cursor = it + 2; + while ( cursor != m_string.end() && + ( isdigit( *cursor ) || *cursor == ';' ) ) { + ++cursor; + } + if ( cursor == m_string.end() || *cursor != 'm' ) { + break; + } + // 'm' -> 0xff + *cursor = AnsiSkippingString::sentinel; + // if we've read an ansi sequence, set the iterator and + // return to the top of the loop + it = cursor + 1; + } + if ( it != m_string.end() ) { + ++m_size; + ++it; + } + } + } + + AnsiSkippingString::AnsiSkippingString( std::string const& text ): + m_string( text ) { + preprocessString(); + } + + AnsiSkippingString::AnsiSkippingString( std::string&& text ): + m_string( CATCH_MOVE( text ) ) { + preprocessString(); + } + + AnsiSkippingString::const_iterator AnsiSkippingString::begin() const { + return const_iterator( m_string ); + } + + AnsiSkippingString::const_iterator AnsiSkippingString::end() const { + return const_iterator( m_string, const_iterator::EndTag{} ); + } + + std::string AnsiSkippingString::substring( const_iterator begin, + const_iterator end ) const { + // There's one caveat here to an otherwise simple substring: when + // making a begin iterator we might have skipped ansi sequences at + // the start. If `begin` here is a begin iterator, skipped over + // initial ansi sequences, we'll use the true beginning of the + // string. Lastly: We need to transform any chars we replaced with + // 0xff back to 'm' + auto str = std::string( begin == this->begin() ? m_string.begin() + : begin.m_it, + end.m_it ); + std::transform( str.begin(), str.end(), str.begin(), []( char c ) { + return c == AnsiSkippingString::sentinel ? 'm' : c; + } ); + return str; + } + + void AnsiSkippingString::const_iterator::tryParseAnsiEscapes() { + // check if we've landed on an ansi sequence, and if so read through + // it + while ( m_it != m_string->end() && *m_it == '\033' && + m_it + 1 != m_string->end() && *( m_it + 1 ) == '[' ) { + auto cursor = m_it + 2; + while ( cursor != m_string->end() && + ( isdigit( *cursor ) || *cursor == ';' ) ) { + ++cursor; + } + if ( cursor == m_string->end() || + *cursor != AnsiSkippingString::sentinel ) { + break; + } + // if we've read an ansi sequence, set the iterator and + // return to the top of the loop + m_it = cursor + 1; + } + } + + void AnsiSkippingString::const_iterator::advance() { + assert( m_it != m_string->end() ); + m_it++; + tryParseAnsiEscapes(); + } + + void AnsiSkippingString::const_iterator::unadvance() { + assert( m_it != m_string->begin() ); + m_it--; + // if *m_it is 0xff, scan back to the \033 and then m_it-- once more + // (and repeat check) + while ( *m_it == AnsiSkippingString::sentinel ) { + while ( *m_it != '\033' ) { + assert( m_it != m_string->begin() ); + m_it--; + } + // if this happens, we must have been a begin iterator that had + // skipped over ansi sequences at the start of a string + assert( m_it != m_string->begin() ); + assert( *m_it == '\033' ); + m_it--; + } + } + + static bool isBoundary( AnsiSkippingString const& line, + AnsiSkippingString::const_iterator it ) { + return it == line.end() || + ( isWhitespace( *it ) && + !isWhitespace( *it.oneBefore() ) ) || + isBreakableBefore( *it ) || + isBreakableAfter( *it.oneBefore() ); + } + + void Column::const_iterator::calcLength() { + m_addHyphen = false; + m_parsedTo = m_lineStart; + AnsiSkippingString const& current_line = m_column.m_string; + + if ( m_parsedTo == current_line.end() ) { + m_lineEnd = m_parsedTo; + return; + } + + assert( m_lineStart != current_line.end() ); + if ( *m_lineStart == '\n' ) { ++m_parsedTo; } + + const auto maxLineLength = m_column.m_width - indentSize(); + std::size_t lineLength = 0; + while ( m_parsedTo != current_line.end() && + lineLength < maxLineLength && *m_parsedTo != '\n' ) { + ++m_parsedTo; + ++lineLength; + } + + // If we encountered a newline before the column is filled, + // then we linebreak at the newline and consider this line + // finished. + if ( lineLength < maxLineLength ) { + m_lineEnd = m_parsedTo; + } else { + // Look for a natural linebreak boundary in the column + // (We look from the end, so that the first found boundary is + // the right one) + m_lineEnd = m_parsedTo; + while ( lineLength > 0 && + !isBoundary( current_line, m_lineEnd ) ) { + --lineLength; + --m_lineEnd; + } + while ( lineLength > 0 && + isWhitespace( *m_lineEnd.oneBefore() ) ) { + --lineLength; + --m_lineEnd; + } + + // If we found one, then that is where we linebreak, otherwise + // we have to split text with a hyphen + if ( lineLength == 0 ) { + m_addHyphen = true; + m_lineEnd = m_parsedTo.oneBefore(); + } + } + } + + size_t Column::const_iterator::indentSize() const { + auto initial = m_lineStart == m_column.m_string.begin() + ? m_column.m_initialIndent + : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; + } + + std::string Column::const_iterator::addIndentAndSuffix( + AnsiSkippingString::const_iterator start, + AnsiSkippingString::const_iterator end ) const { + std::string ret; + const auto desired_indent = indentSize(); + // ret.reserve( desired_indent + (end - start) + m_addHyphen ); + ret.append( desired_indent, ' ' ); + // ret.append( start, end ); + ret += m_column.m_string.substring( start, end ); + if ( m_addHyphen ) { ret.push_back( '-' ); } + + return ret; + } + + Column::const_iterator::const_iterator( Column const& column ): + m_column( column ), + m_lineStart( column.m_string.begin() ), + m_lineEnd( column.m_string.begin() ), + m_parsedTo( column.m_string.begin() ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || + m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if ( m_lineStart == m_lineEnd ) { + m_lineStart = m_column.m_string.end(); + } + } + + std::string Column::const_iterator::operator*() const { + assert( m_lineStart <= m_parsedTo ); + return addIndentAndSuffix( m_lineStart, m_lineEnd ); + } + + Column::const_iterator& Column::const_iterator::operator++() { + m_lineStart = m_lineEnd; + AnsiSkippingString const& current_line = m_column.m_string; + if ( m_lineStart != current_line.end() && *m_lineStart == '\n' ) { + m_lineStart++; + } else { + while ( m_lineStart != current_line.end() && + isWhitespace( *m_lineStart ) ) { + ++m_lineStart; + } + } + + if ( m_lineStart != current_line.end() ) { calcLength(); } + return *this; + } + + Column::const_iterator Column::const_iterator::operator++( int ) { + const_iterator prev( *this ); + operator++(); + return prev; + } + + std::ostream& operator<<( std::ostream& os, Column const& col ) { + bool first = true; + for ( auto line : col ) { + if ( first ) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Column Spacer( size_t spaceWidth ) { + Column ret{ "" }; + ret.width( spaceWidth ); + return ret; + } + + Columns::iterator::iterator( Columns const& columns, EndTag ): + m_columns( columns.m_columns ), m_activeIterators( 0 ) { + + m_iterators.reserve( m_columns.size() ); + for ( auto const& col : m_columns ) { + m_iterators.push_back( col.end() ); + } + } + + Columns::iterator::iterator( Columns const& columns ): + m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) { + + m_iterators.reserve( m_columns.size() ); + for ( auto const& col : m_columns ) { + m_iterators.push_back( col.begin() ); + } + } + + std::string Columns::iterator::operator*() const { + std::string row, padding; + + for ( size_t i = 0; i < m_columns.size(); ++i ) { + const auto width = m_columns[i].width(); + if ( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding; + row += col; + + padding.clear(); + if ( col.size() < width ) { + padding.append( width - col.size(), ' ' ); + } + } else { + padding.append( width, ' ' ); + } + } + return row; + } + + Columns::iterator& Columns::iterator::operator++() { + for ( size_t i = 0; i < m_columns.size(); ++i ) { + if ( m_iterators[i] != m_columns[i].end() ) { + ++m_iterators[i]; + } + } + return *this; + } + + Columns::iterator Columns::iterator::operator++( int ) { + iterator prev( *this ); + operator++(); + return prev; + } + + std::ostream& operator<<( std::ostream& os, Columns const& cols ) { + bool first = true; + for ( auto line : cols ) { + if ( first ) { + first = false; + } else { + os << '\n'; + } + os << line; + } + return os; + } + + Columns operator+( Column const& lhs, Column const& rhs ) { + Columns cols; + cols += lhs; + cols += rhs; + return cols; + } + Columns operator+( Column&& lhs, Column&& rhs ) { + Columns cols; + cols += CATCH_MOVE( lhs ); + cols += CATCH_MOVE( rhs ); + return cols; + } + + Columns& operator+=( Columns& lhs, Column const& rhs ) { + lhs.m_columns.push_back( rhs ); + return lhs; + } + Columns& operator+=( Columns& lhs, Column&& rhs ) { + lhs.m_columns.push_back( CATCH_MOVE( rhs ) ); + return lhs; + } + Columns operator+( Columns const& lhs, Column const& rhs ) { + auto combined( lhs ); + combined += rhs; + return combined; + } + Columns operator+( Columns&& lhs, Column&& rhs ) { + lhs += CATCH_MOVE( rhs ); + return CATCH_MOVE( lhs ); + } + + } // namespace TextFlow +} // namespace Catch + + + + +#include <exception> + +namespace Catch { + bool uncaught_exceptions() { +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + return false; +#elif defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + return std::uncaught_exceptions() > 0; +#else + return std::uncaught_exception(); +#endif + } +} // end namespace Catch + + + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( normaliseString( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == normaliseString( str ); + case WildcardAtStart: + return endsWith( normaliseString( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( normaliseString( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( normaliseString( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::normaliseString( std::string const& str ) const { + return trim( m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str ); + } +} + + +// Note: swapping these two includes around causes MSVC to error out +// while in /permissive- mode. No, I don't know why. +// Tested on VS 2019, 18.{3, 4}.x + +#include <cstdint> +#include <iomanip> +#include <type_traits> + +namespace Catch { + +namespace { + + size_t trailingBytes(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return 2; + } + if ((c & 0xF0) == 0xE0) { + return 3; + } + if ((c & 0xF8) == 0xF0) { + return 4; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + uint32_t headerValue(unsigned char c) { + if ((c & 0xE0) == 0xC0) { + return c & 0x1F; + } + if ((c & 0xF0) == 0xE0) { + return c & 0x0F; + } + if ((c & 0xF8) == 0xF0) { + return c & 0x07; + } + CATCH_INTERNAL_ERROR("Invalid multibyte utf-8 start byte encountered"); + } + + void hexEscapeChar(std::ostream& os, unsigned char c) { + std::ios_base::fmtflags f(os.flags()); + os << "\\x" + << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>(c); + os.flags(f); + } + + constexpr bool shouldNewline(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Newline)); + } + + constexpr bool shouldIndent(XmlFormatting fmt) { + return !!(static_cast<std::underlying_type_t<XmlFormatting>>(fmt & XmlFormatting::Indent)); + } + +} // anonymous namespace + + void XmlEncode::encodeTo( std::ostream& os ) const { + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t idx = 0; idx < m_str.size(); ++ idx ) { + unsigned char c = static_cast<unsigned char>(m_str[idx]); + switch (c) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if (idx > 2 && m_str[idx - 1] == ']' && m_str[idx - 2] == ']') + os << ">"; + else + os << c; + break; + + case '\"': + if (m_forWhat == ForAttributes) + os << """; + else + os << c; + break; + + default: + // Check for control characters and invalid utf-8 + + // Escape control characters in standard ascii + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + if (c < 0x09 || (c > 0x0D && c < 0x20) || c == 0x7F) { + hexEscapeChar(os, c); + break; + } + + // Plain ASCII: Write it to stream + if (c < 0x7F) { + os << c; + break; + } + + // UTF-8 territory + // Check if the encoding is valid and if it is not, hex escape bytes. + // Important: We do not check the exact decoded values for validity, only the encoding format + // First check that this bytes is a valid lead byte: + // This means that it is not encoded as 1111 1XXX + // Or as 10XX XXXX + if (c < 0xC0 || + c >= 0xF8) { + hexEscapeChar(os, c); + break; + } + + auto encBytes = trailingBytes(c); + // Are there enough bytes left to avoid accessing out-of-bounds memory? + if (idx + encBytes - 1 >= m_str.size()) { + hexEscapeChar(os, c); + break; + } + // The header is valid, check data + // The next encBytes bytes must together be a valid utf-8 + // This means: bitpattern 10XX XXXX and the extracted value is sane (ish) + bool valid = true; + uint32_t value = headerValue(c); + for (std::size_t n = 1; n < encBytes; ++n) { + unsigned char nc = static_cast<unsigned char>(m_str[idx + n]); + valid &= ((nc & 0xC0) == 0x80); + value = (value << 6) | (nc & 0x3F); + } + + if ( + // Wrong bit pattern of following bytes + (!valid) || + // Overlong encodings + (value < 0x80) || + (0x80 <= value && value < 0x800 && encBytes > 2) || + (0x800 < value && value < 0x10000 && encBytes > 3) || + // Encoded value out of range + (value >= 0x110000) + ) { + hexEscapeChar(os, c); + break; + } + + // If we got here, this is in fact a valid(ish) utf-8 sequence + for (std::size_t n = 0; n < encBytes; ++n) { + os << m_str[idx + n]; + } + idx += encBytes - 1; + break; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) + : m_writer( writer ), + m_fmt(fmt) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ), + m_fmt(other.m_fmt) + { + other.m_writer = nullptr; + other.m_fmt = XmlFormatting::None; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + m_fmt = other.m_fmt; + other.m_fmt = XmlFormatting::None; + return *this; + } + + + XmlWriter::ScopedElement::~ScopedElement() { + if (m_writer) { + m_writer->endElement(m_fmt); + } + } + + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeText( StringRef text, XmlFormatting fmt ) { + m_writer->writeText( text, fmt ); + return *this; + } + + XmlWriter::ScopedElement& + XmlWriter::ScopedElement::writeAttribute( StringRef name, + StringRef attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while (!m_tags.empty()) { + endElement(); + } + newlineIfNecessary(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { + ensureTagClosed(); + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + m_indent += " "; + } + m_os << '<' << name; + m_tags.push_back( name ); + m_tagIsOpen = true; + applyFormatting(fmt); + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { + ScopedElement scoped( this, fmt ); + startElement( name, fmt ); + return scoped; + } + + XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { + m_indent = m_indent.substr(0, m_indent.size() - 2); + + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } else { + newlineIfNecessary(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "</" << m_tags.back() << '>'; + } + m_os << std::flush; + applyFormatting(fmt); + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, + StringRef attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, bool attribute ) { + writeAttribute(name, (attribute ? "true"_sr : "false"_sr)); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( StringRef name, + char const* attribute ) { + writeAttribute( name, StringRef( attribute ) ); + return *this; + } + + XmlWriter& XmlWriter::writeText( StringRef text, XmlFormatting fmt ) { + CATCH_ENFORCE(!m_tags.empty(), "Cannot write text as top level element"); + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if (tagWasOpen && shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << XmlEncode( text, XmlEncode::ForTextNodes ); + applyFormatting(fmt); + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( StringRef text, XmlFormatting fmt ) { + ensureTagClosed(); + if (shouldIndent(fmt)) { + m_os << m_indent; + } + m_os << "<!-- " << text << " -->"; + applyFormatting(fmt); + return *this; + } + + void XmlWriter::writeStylesheetRef( StringRef url ) { + m_os << R"(<?xml-stylesheet type="text/xsl" href=")" << url << R"("?>)" << '\n'; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << '>' << std::flush; + newlineIfNecessary(); + m_tagIsOpen = false; + } + } + + void XmlWriter::applyFormatting(XmlFormatting fmt) { + m_needsNewline = shouldNewline(fmt); + } + + void XmlWriter::writeDeclaration() { + m_os << R"(<?xml version="1.0" encoding="UTF-8"?>)" << '\n'; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << '\n' << std::flush; + m_needsNewline = false; + } + } +} + + + + + +namespace Catch { +namespace Matchers { + + std::string MatcherUntypedBase::toString() const { + if (m_cachedToString.empty()) { + m_cachedToString = describe(); + } + return m_cachedToString; + } + + MatcherUntypedBase::~MatcherUntypedBase() = default; + +} // namespace Matchers +} // namespace Catch + + + + +namespace Catch { +namespace Matchers { + + std::string IsEmptyMatcher::describe() const { + return "is empty"; + } + + std::string HasSizeMatcher::describe() const { + ReusableStringStream sstr; + sstr << "has size == " << m_target_size; + return sstr.str(); + } + + IsEmptyMatcher IsEmpty() { + return {}; + } + + HasSizeMatcher SizeIs(std::size_t sz) { + return HasSizeMatcher{ sz }; + } + +} // end namespace Matchers +} // end namespace Catch + + + +namespace Catch { +namespace Matchers { + +bool ExceptionMessageMatcher::match(std::exception const& ex) const { + return ex.what() == m_message; +} + +std::string ExceptionMessageMatcher::describe() const { + return "exception message matches \"" + m_message + '"'; +} + +ExceptionMessageMatcher Message(std::string const& message) { + return ExceptionMessageMatcher(message); +} + +} // namespace Matchers +} // namespace Catch + + + +#include <algorithm> +#include <cmath> +#include <cstdlib> +#include <cstdint> +#include <sstream> +#include <iomanip> +#include <limits> + + +namespace Catch { +namespace { + + template <typename FP> + bool almostEqualUlps(FP lhs, FP rhs, uint64_t maxUlpDiff) { + // Comparison with NaN should always be false. + // This way we can rule it out before getting into the ugly details + if (Catch::isnan(lhs) || Catch::isnan(rhs)) { + return false; + } + + // This should also handle positive and negative zeros, infinities + const auto ulpDist = ulpDistance(lhs, rhs); + + return ulpDist <= maxUlpDiff; + } + + +template <typename FP> +FP step(FP start, FP direction, uint64_t steps) { + for (uint64_t i = 0; i < steps; ++i) { + start = Catch::nextafter(start, direction); + } + return start; +} + +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} + +template <typename FloatingPoint> +void write(std::ostream& out, FloatingPoint num) { + out << std::scientific + << std::setprecision(std::numeric_limits<FloatingPoint>::max_digits10 - 1) + << num; +} + +} // end anonymous namespace + +namespace Matchers { +namespace Detail { + + enum class FloatingPointKind : uint8_t { + Float, + Double + }; + +} // end namespace Detail + + + WithinAbsMatcher::WithinAbsMatcher(double target, double margin) + :m_target{ target }, m_margin{ margin } { + CATCH_ENFORCE(margin >= 0, "Invalid margin: " << margin << '.' + << " Margin has to be non-negative."); + } + + // Performs equivalent check of std::fabs(lhs - rhs) <= margin + // But without the subtraction to allow for INFINITY in comparison + bool WithinAbsMatcher::match(double const& matchee) const { + return (matchee + m_margin >= m_target) && (m_target + m_margin >= matchee); + } + + std::string WithinAbsMatcher::describe() const { + return "is within " + ::Catch::Detail::stringify(m_margin) + " of " + ::Catch::Detail::stringify(m_target); + } + + + WithinUlpsMatcher::WithinUlpsMatcher(double target, uint64_t ulps, Detail::FloatingPointKind baseType) + :m_target{ target }, m_ulps{ ulps }, m_type{ baseType } { + CATCH_ENFORCE(m_type == Detail::FloatingPointKind::Double + || m_ulps < (std::numeric_limits<uint32_t>::max)(), + "Provided ULP is impossibly large for a float comparison."); + CATCH_ENFORCE( std::numeric_limits<double>::is_iec559, + "WithinUlp matcher only supports platforms with " + "IEEE-754 compatible floating point representation" ); + } + +#if defined(__clang__) +#pragma clang diagnostic push +// Clang <3.5 reports on the default branch in the switch below +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + + bool WithinUlpsMatcher::match(double const& matchee) const { + switch (m_type) { + case Detail::FloatingPointKind::Float: + return almostEqualUlps<float>(static_cast<float>(matchee), static_cast<float>(m_target), m_ulps); + case Detail::FloatingPointKind::Double: + return almostEqualUlps<double>(matchee, m_target, m_ulps); + default: + CATCH_INTERNAL_ERROR( "Unknown Detail::FloatingPointKind value" ); + } + } + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + std::string WithinUlpsMatcher::describe() const { + std::stringstream ret; + + ret << "is within " << m_ulps << " ULPs of "; + + if (m_type == Detail::FloatingPointKind::Float) { + write(ret, static_cast<float>(m_target)); + ret << 'f'; + } else { + write(ret, m_target); + } + + ret << " (["; + if (m_type == Detail::FloatingPointKind::Double) { + write( ret, + step( m_target, + -std::numeric_limits<double>::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( m_target, + std::numeric_limits<double>::infinity(), + m_ulps ) ); + } else { + // We have to cast INFINITY to float because of MinGW, see #1782 + write( ret, + step( static_cast<float>( m_target ), + -std::numeric_limits<float>::infinity(), + m_ulps ) ); + ret << ", "; + write( ret, + step( static_cast<float>( m_target ), + std::numeric_limits<float>::infinity(), + m_ulps ) ); + } + ret << "])"; + + return ret.str(); + } + + WithinRelMatcher::WithinRelMatcher(double target, double epsilon): + m_target(target), + m_epsilon(epsilon){ + CATCH_ENFORCE(m_epsilon >= 0., "Relative comparison with epsilon < 0 does not make sense."); + CATCH_ENFORCE(m_epsilon < 1., "Relative comparison with epsilon >= 1 does not make sense."); + } + + bool WithinRelMatcher::match(double const& matchee) const { + const auto relMargin = m_epsilon * (std::max)(std::fabs(matchee), std::fabs(m_target)); + return marginComparison(matchee, m_target, + std::isinf(relMargin)? 0 : relMargin); + } + + std::string WithinRelMatcher::describe() const { + Catch::ReusableStringStream sstr; + sstr << "and " << ::Catch::Detail::stringify(m_target) << " are within " << m_epsilon * 100. << "% of each other"; + return sstr.str(); + } + + +WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Double); +} + +WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff) { + return WithinUlpsMatcher(target, maxUlpDiff, Detail::FloatingPointKind::Float); +} + +WithinAbsMatcher WithinAbs(double target, double margin) { + return WithinAbsMatcher(target, margin); +} + +WithinRelMatcher WithinRel(double target, double eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(double target) { + return WithinRelMatcher(target, std::numeric_limits<double>::epsilon() * 100); +} + +WithinRelMatcher WithinRel(float target, float eps) { + return WithinRelMatcher(target, eps); +} + +WithinRelMatcher WithinRel(float target) { + return WithinRelMatcher(target, std::numeric_limits<float>::epsilon() * 100); +} + + + +bool IsNaNMatcher::match( double const& matchee ) const { + return std::isnan( matchee ); +} + +std::string IsNaNMatcher::describe() const { + using namespace std::string_literals; + return "is NaN"s; +} + +IsNaNMatcher IsNaN() { return IsNaNMatcher(); } + + } // namespace Matchers +} // namespace Catch + + + + +std::string Catch::Matchers::Detail::finalizeDescription(const std::string& desc) { + if (desc.empty()) { + return "matches undescribed predicate"; + } else { + return "matches predicate: \"" + desc + '"'; + } +} + + + +namespace Catch { + namespace Matchers { + std::string AllTrueMatcher::describe() const { return "contains only true"; } + + AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } + + std::string NoneTrueMatcher::describe() const { return "contains no true"; } + + NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } + + std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } + + AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } + } // namespace Matchers +} // namespace Catch + + + +#include <regex> + +namespace Catch { +namespace Matchers { + + CasedString::CasedString( std::string const& str, CaseSensitive caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + } + StringRef CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::Yes + ? StringRef() + : " (case insensitive)"_sr; + } + + + StringMatcherBase::StringMatcherBase( StringRef operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { + } + + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += '"'; + description += m_comparator.caseSensitivitySuffix(); + return description; + } + + StringEqualsMatcher::StringEqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals"_sr, comparator ) {} + + bool StringEqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; + } + + + StringContainsMatcher::StringContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains"_sr, comparator ) {} + + bool StringContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with"_sr, comparator ) {} + + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with"_sr, comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } + + + + RegexMatcher::RegexMatcher(std::string regex, CaseSensitive caseSensitivity): m_regex(CATCH_MOVE(regex)), m_caseSensitivity(caseSensitivity) {} + + bool RegexMatcher::match(std::string const& matchee) const { + auto flags = std::regex::ECMAScript; // ECMAScript is the default syntax option anyway + if (m_caseSensitivity == CaseSensitive::No) { + flags |= std::regex::icase; + } + auto reg = std::regex(m_regex, flags); + return std::regex_match(matchee, reg); + } + + std::string RegexMatcher::describe() const { + return "matches " + ::Catch::Detail::stringify(m_regex) + ((m_caseSensitivity == CaseSensitive::Yes)? " case sensitively" : " case insensitively"); + } + + + StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity ) { + return StringEqualsMatcher( CasedString( str, caseSensitivity) ); + } + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity ) { + return StringContainsMatcher( CasedString( str, caseSensitivity) ); + } + EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return EndsWithMatcher( CasedString( str, caseSensitivity) ); + } + StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity ) { + return StartsWithMatcher( CasedString( str, caseSensitivity) ); + } + + RegexMatcher Matches(std::string const& regex, CaseSensitive caseSensitivity) { + return RegexMatcher(regex, caseSensitivity); + } + +} // namespace Matchers +} // namespace Catch + + + +namespace Catch { +namespace Matchers { + MatcherGenericBase::~MatcherGenericBase() = default; + + namespace Detail { + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end) { + std::string description; + std::size_t combined_size = 4; + for ( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + combined_size += desc->size(); + } + combined_size += static_cast<size_t>(descriptions_end - descriptions_begin - 1) * combine.size(); + + description.reserve(combined_size); + + description += "( "; + bool first = true; + for( auto desc = descriptions_begin; desc != descriptions_end; ++desc ) { + if( first ) + first = false; + else + description += combine; + description += *desc; + } + description += " )"; + return description; + } + + } // namespace Detail +} // namespace Matchers +} // namespace Catch + + + + +namespace Catch { + + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertionhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( CATCH_MOVE(exceptionMessage), matcher ); + handler.handleExpr( expr ); + } + +} // namespace Catch + + + +#include <ostream> + +namespace Catch { + + AutomakeReporter::~AutomakeReporter() = default; + + void AutomakeReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + // Possible values to emit are PASS, XFAIL, SKIP, FAIL, XPASS and ERROR. + m_stream << ":test-result: "; + if ( _testCaseStats.totals.testCases.skipped > 0 ) { + m_stream << "SKIP"; + } else if (_testCaseStats.totals.assertions.allPassed()) { + m_stream << "PASS"; + } else if (_testCaseStats.totals.assertions.allOk()) { + m_stream << "XFAIL"; + } else { + m_stream << "FAIL"; + } + m_stream << ' ' << _testCaseStats.testInfo->name << '\n'; + StreamingReporterBase::testCaseEnded(_testCaseStats); + } + + void AutomakeReporter::skipTest(TestCaseInfo const& testInfo) { + m_stream << ":test-result: SKIP " << testInfo.name << '\n'; + } + +} // end namespace Catch + + + + + + +namespace Catch { + ReporterBase::ReporterBase( ReporterConfig&& config ): + IEventListener( config.fullConfig() ), + m_wrapped_stream( CATCH_MOVE(config).takeStream() ), + m_stream( m_wrapped_stream->stream() ), + m_colour( makeColourImpl( config.colourMode(), m_wrapped_stream.get() ) ), + m_customOptions( config.customOptions() ) + {} + + ReporterBase::~ReporterBase() = default; + + void ReporterBase::listReporters( + std::vector<ReporterDescription> const& descriptions ) { + defaultListReporters(m_stream, descriptions, m_config->verbosity()); + } + + void ReporterBase::listListeners( + std::vector<ListenerDescription> const& descriptions ) { + defaultListListeners( m_stream, descriptions ); + } + + void ReporterBase::listTests(std::vector<TestCaseHandle> const& tests) { + defaultListTests(m_stream, + m_colour.get(), + tests, + m_config->hasTestFilters(), + m_config->verbosity()); + } + + void ReporterBase::listTags(std::vector<TagInfo> const& tags) { + defaultListTags( m_stream, tags, m_config->hasTestFilters() ); + } + +} // namespace Catch + + + + +#include <ostream> + +namespace Catch { +namespace { + + // Colour::LightGrey + static constexpr Colour::Code compactDimColour = Colour::FileName; + +#ifdef CATCH_PLATFORM_MAC + static constexpr Catch::StringRef compactFailedString = "FAILED"_sr; + static constexpr Catch::StringRef compactPassedString = "PASSED"_sr; +#else + static constexpr Catch::StringRef compactFailedString = "failed"_sr; + static constexpr Catch::StringRef compactPassedString = "passed"_sr; +#endif + +// Implementation of CompactReporter formatting +class AssertionPrinter { +public: + AssertionPrinter& operator= (AssertionPrinter const&) = delete; + AssertionPrinter(AssertionPrinter const&) = delete; + AssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages, ColourImpl* colourImpl_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(_printInfoMessages) + , colourImpl(colourImpl_) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(Colour::ResultSuccess, compactPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) + printResultType(Colour::ResultSuccess, compactFailedString + " - but was ok"_sr); + else + printResultType(Colour::Error, compactFailedString); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(Colour::Error, compactFailedString); + printIssue("unexpected exception with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(Colour::Error, compactFailedString); + printIssue("fatal error condition with message:"); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(Colour::Error, compactFailedString); + printIssue("expected exception, got none"); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType(Colour::None, "info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType(Colour::None, "warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(Colour::Error, compactFailedString); + printIssue("explicitly"); + printRemainingMessages(Colour::None); + break; + case ResultWas::ExplicitSkip: + printResultType(Colour::Skip, "skipped"_sr); + printMessage(); + printRemainingMessages(); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType(Colour::Error, "** internal error **"); + break; + } + } + +private: + void printSourceInfo() const { + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ':'; + } + + void printResultType(Colour::Code colour, StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << ' ' << passOrFail; + stream << ':'; + } + } + + void printIssue(char const* issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + { + stream << colourImpl->guardColour(compactDimColour) << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour(compactDimColour) << " for: "; + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = compactDimColour) { + if (itMessage == messages.end()) + return; + + const auto itEnd = messages.cend(); + const auto N = static_cast<std::size_t>(itEnd - itMessage); + + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; + + while (itMessage != itEnd) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + printMessage(); + if (itMessage != itEnd) { + stream << colourImpl->guardColour(compactDimColour) << " and"; + } + continue; + } + ++itMessage; + } + } + +private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> const& messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; + ColourImpl* colourImpl; +}; + +} // anon namespace + + std::string CompactReporter::getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + void CompactReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; + } + + void CompactReporter::testRunStarting( TestRunInfo const& ) { + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) + << "Filters: " + << m_config->testSpec() + << '\n'; + } + m_stream << "RNG seed: " << getSeed() << '\n'; + } + + void CompactReporter::assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip ) + return; + printInfoMessages = false; + } + + AssertionPrinter printer( m_stream, _assertionStats, printInfoMessages, m_colour.get() ); + printer.print(); + + m_stream << '\n' << std::flush; + } + + void CompactReporter::sectionEnded(SectionStats const& _sectionStats) { + double dur = _sectionStats.durationInSeconds; + if ( shouldShowDuration( *m_config, dur ) ) { + m_stream << getFormattedDuration( dur ) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; + } + } + + void CompactReporter::testRunEnded( TestRunStats const& _testRunStats ) { + printTestRunTotals( m_stream, *m_colour, _testRunStats.totals ); + m_stream << "\n\n" << std::flush; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + CompactReporter::~CompactReporter() = default; + +} // end namespace Catch + + + + +#include <cstdio> + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled and default is missing) is enabled +#endif + +#if defined(__clang__) +# pragma clang diagnostic push +// For simplicity, benchmarking-only helpers are always enabled +# pragma clang diagnostic ignored "-Wunused-function" +#endif + + + +namespace Catch { + +namespace { + +// Formatter impl for ConsoleReporter +class ConsoleAssertionPrinter { +public: + ConsoleAssertionPrinter& operator= (ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(ConsoleAssertionPrinter const&) = delete; + ConsoleAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, ColourImpl* colourImpl_, bool _printInfoMessages) + : stream(_stream), + stats(_stats), + result(_stats.assertionResult), + colour(Colour::None), + messages(_stats.infoMessages), + colourImpl(colourImpl_), + printInfoMessages(_printInfoMessages) { + switch (result.getResultType()) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"_sr; + //if( result.hasMessage() ) + if (messages.size() == 1) + messageLabel = "with message"_sr; + if (messages.size() > 1) + messageLabel = "with messages"_sr; + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"_sr; + } else { + colour = Colour::Error; + passOrFail = "FAILED"_sr; + } + if (messages.size() == 1) + messageLabel = "with message"_sr; + if (messages.size() > 1) + messageLabel = "with messages"_sr; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + // todo switch + switch (messages.size()) { case 0: + messageLabel = "due to unexpected exception with "_sr; + break; + case 1: + messageLabel = "due to unexpected exception with message"_sr; + break; + default: + messageLabel = "due to unexpected exception with messages"_sr; + break; + } + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "due to a fatal error condition"_sr; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"_sr; + messageLabel = "because no exception was thrown where one was expected"_sr; + break; + case ResultWas::Info: + messageLabel = "info"_sr; + break; + case ResultWas::Warning: + messageLabel = "warning"_sr; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"_sr; + colour = Colour::Error; + if (messages.size() == 1) + messageLabel = "explicitly with message"_sr; + if (messages.size() > 1) + messageLabel = "explicitly with messages"_sr; + break; + case ResultWas::ExplicitSkip: + colour = Colour::Skip; + passOrFail = "SKIPPED"_sr; + if (messages.size() == 1) + messageLabel = "explicitly with message"_sr; + if (messages.size() > 1) + messageLabel = "explicitly with messages"_sr; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"_sr; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if (stats.totals.assertions.total() > 0) { + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } else { + stream << '\n'; + } + printMessage(); + } + +private: + void printResultType() const { + if (!passOrFail.empty()) { + stream << colourImpl->guardColour(colour) << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << colourImpl->guardColour( Colour::OriginalExpression ) + << " " << result.getExpressionInMacro() << '\n'; + } + } + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << "with expansion:\n"; + stream << colourImpl->guardColour( Colour::ReconstructedExpression ) + << TextFlow::Column( result.getExpandedExpression() ) + .indent( 2 ) + << '\n'; + } + } + void printMessage() const { + if (!messageLabel.empty()) + stream << messageLabel << ':' << '\n'; + for (auto const& msg : messages) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || msg.type != ResultWas::Info) + stream << TextFlow::Column(msg.message).indent(2) << '\n'; + } + } + void printSourceInfo() const { + stream << colourImpl->guardColour( Colour::FileName ) + << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + StringRef passOrFail; + StringRef messageLabel; + std::vector<MessageInfo> const& messages; + ColourImpl* colourImpl; + bool printInfoMessages; +}; + +std::size_t makeRatio( std::uint64_t number, std::uint64_t total ) { + const auto ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number / total : 0; + return (ratio == 0 && number > 0) ? 1 : static_cast<std::size_t>(ratio); +} + +std::size_t& +findMax( std::size_t& i, std::size_t& j, std::size_t& k, std::size_t& l ) { + if (i > j && i > k && i > l) + return i; + else if (j > k && j > l) + return j; + else if (k > l) + return k; + else + return l; +} + +struct ColumnBreak {}; +struct RowBreak {}; +struct OutputFlush {}; + +class Duration { + enum class Unit : uint8_t { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000 * s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000 * s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60 * s_nanosecondsInASecond; + + double m_inNanoseconds; + Unit m_units; + +public: + explicit Duration(double inNanoseconds, Unit units = Unit::Auto) + : m_inNanoseconds(inNanoseconds), + m_units(units) { + if (m_units == Unit::Auto) { + if (m_inNanoseconds < s_nanosecondsInAMicrosecond) + m_units = Unit::Nanoseconds; + else if (m_inNanoseconds < s_nanosecondsInAMillisecond) + m_units = Unit::Microseconds; + else if (m_inNanoseconds < s_nanosecondsInASecond) + m_units = Unit::Milliseconds; + else if (m_inNanoseconds < s_nanosecondsInAMinute) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch (m_units) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMicrosecond); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMillisecond); + case Unit::Seconds: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInASecond); + case Unit::Minutes: + return m_inNanoseconds / static_cast<double>(s_nanosecondsInAMinute); + default: + return m_inNanoseconds; + } + } + StringRef unitsAsString() const { + switch (m_units) { + case Unit::Nanoseconds: + return "ns"_sr; + case Unit::Microseconds: + return "us"_sr; + case Unit::Milliseconds: + return "ms"_sr; + case Unit::Seconds: + return "s"_sr; + case Unit::Minutes: + return "m"_sr; + default: + return "** internal error **"_sr; + } + + } + friend auto operator << (std::ostream& os, Duration const& duration) -> std::ostream& { + return os << duration.value() << ' ' << duration.unitsAsString(); + } +}; +} // end anon namespace + +enum class Justification : uint8_t { + Left, + Right +}; + +struct ColumnInfo { + std::string name; + std::size_t width; + Justification justification; +}; + +class TablePrinter { + std::ostream& m_os; + std::vector<ColumnInfo> m_columnInfos; + ReusableStringStream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; + +public: + TablePrinter( std::ostream& os, std::vector<ColumnInfo> columnInfos ) + : m_os( os ), + m_columnInfos( CATCH_MOVE( columnInfos ) ) {} + + auto columnInfos() const -> std::vector<ColumnInfo> const& { + return m_columnInfos; + } + + void open() { + if (!m_isOpen) { + m_isOpen = true; + *this << RowBreak(); + + TextFlow::Columns headerCols; + for (auto const& info : m_columnInfos) { + assert(info.width > 2); + headerCols += TextFlow::Column(info.name).width(info.width - 2); + headerCols += TextFlow::Spacer( 2 ); + } + m_os << headerCols << '\n'; + + m_os << lineOfChars('-') << '\n'; + } + } + void close() { + if (m_isOpen) { + *this << RowBreak(); + m_os << '\n' << std::flush; + m_isOpen = false; + } + } + + template<typename T> + friend TablePrinter& operator<< (TablePrinter& tp, T const& value) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator<< (TablePrinter& tp, ColumnBreak) { + auto colStr = tp.m_oss.str(); + const auto strSize = colStr.size(); + tp.m_oss.str(""); + tp.open(); + if (tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size() - 1)) { + tp.m_currentColumn = -1; + tp.m_os << '\n'; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = (strSize + 1 < colInfo.width) + ? std::string(colInfo.width - (strSize + 1), ' ') + : std::string(); + if (colInfo.justification == Justification::Left) + tp.m_os << colStr << padding << ' '; + else + tp.m_os << padding << colStr << ' '; + return tp; + } + + friend TablePrinter& operator<< (TablePrinter& tp, RowBreak) { + if (tp.m_currentColumn > 0) { + tp.m_os << '\n'; + tp.m_currentColumn = -1; + } + return tp; + } + + friend TablePrinter& operator<<(TablePrinter& tp, OutputFlush) { + tp.m_os << std::flush; + return tp; + } +}; + +ConsoleReporter::ConsoleReporter(ReporterConfig&& config): + StreamingReporterBase( CATCH_MOVE( config ) ), + m_tablePrinter(Detail::make_unique<TablePrinter>(m_stream, + [&config]() -> std::vector<ColumnInfo> { + if (config.fullConfig()->benchmarkNoAnalysis()) + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, + { " samples", 14, Justification::Right }, + { " iterations", 14, Justification::Right }, + { " mean", 14, Justification::Right } + }; + } + else + { + return{ + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH - 43, Justification::Left }, + { "samples mean std dev", 14, Justification::Right }, + { "iterations low mean low std dev", 14, Justification::Right }, + { "est run time high mean high std dev", 14, Justification::Right } + }; + } + }())) {} +ConsoleReporter::~ConsoleReporter() = default; + +std::string ConsoleReporter::getDescription() { + return "Reports test results as plain lines of text"; +} + +void ConsoleReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "No test cases matched '" << unmatchedSpec << "'\n"; +} + +void ConsoleReporter::reportInvalidTestSpec( StringRef arg ) { + m_stream << "Invalid Filter: " << arg << '\n'; +} + +void ConsoleReporter::assertionStarting(AssertionInfo const&) {} + +void ConsoleReporter::assertionEnded(AssertionStats const& _assertionStats) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + // Drop out if result was successful but we're not printing them. + // TODO: Make configurable whether skips should be printed + if (!includeResults && result.getResultType() != ResultWas::Warning && result.getResultType() != ResultWas::ExplicitSkip) + return; + + lazyPrint(); + + ConsoleAssertionPrinter printer(m_stream, _assertionStats, m_colour.get(), includeResults); + printer.print(); + m_stream << '\n' << std::flush; +} + +void ConsoleReporter::sectionStarting(SectionInfo const& _sectionInfo) { + m_tablePrinter->close(); + m_headerPrinted = false; + StreamingReporterBase::sectionStarting(_sectionInfo); +} +void ConsoleReporter::sectionEnded(SectionStats const& _sectionStats) { + m_tablePrinter->close(); + if (_sectionStats.missingAssertions) { + lazyPrint(); + auto guard = + m_colour->guardColour( Colour::ResultError ).engage( m_stream ); + if (m_sectionStack.size() > 1) + m_stream << "\nNo assertions in section"; + else + m_stream << "\nNo assertions in test case"; + m_stream << " '" << _sectionStats.sectionInfo.name << "'\n\n" << std::flush; + } + double dur = _sectionStats.durationInSeconds; + if (shouldShowDuration(*m_config, dur)) { + m_stream << getFormattedDuration(dur) << " s: " << _sectionStats.sectionInfo.name << '\n' << std::flush; + } + if (m_headerPrinted) { + m_headerPrinted = false; + } + StreamingReporterBase::sectionEnded(_sectionStats); +} + +void ConsoleReporter::benchmarkPreparing( StringRef name ) { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = TextFlow::Column( static_cast<std::string>( name ) ) + .width( m_tablePrinter->columnInfos()[0].width - 2 ); + + bool firstLine = true; + for (auto line : nameCol) { + if (!firstLine) + (*m_tablePrinter) << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + (*m_tablePrinter) << line << ColumnBreak(); + } +} + +void ConsoleReporter::benchmarkStarting(BenchmarkInfo const& info) { + (*m_tablePrinter) << info.samples << ColumnBreak() + << info.iterations << ColumnBreak(); + if ( !m_config->benchmarkNoAnalysis() ) { + ( *m_tablePrinter ) + << Duration( info.estimatedDuration ) << ColumnBreak(); + } + ( *m_tablePrinter ) << OutputFlush{}; +} +void ConsoleReporter::benchmarkEnded(BenchmarkStats<> const& stats) { + if (m_config->benchmarkNoAnalysis()) + { + (*m_tablePrinter) << Duration(stats.mean.point.count()) << ColumnBreak(); + } + else + { + (*m_tablePrinter) << ColumnBreak() + << Duration(stats.mean.point.count()) << ColumnBreak() + << Duration(stats.mean.lower_bound.count()) << ColumnBreak() + << Duration(stats.mean.upper_bound.count()) << ColumnBreak() << ColumnBreak() + << Duration(stats.standardDeviation.point.count()) << ColumnBreak() + << Duration(stats.standardDeviation.lower_bound.count()) << ColumnBreak() + << Duration(stats.standardDeviation.upper_bound.count()) << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak() << ColumnBreak(); + } +} + +void ConsoleReporter::benchmarkFailed( StringRef error ) { + auto guard = m_colour->guardColour( Colour::Red ).engage( m_stream ); + (*m_tablePrinter) + << "Benchmark failed (" << error << ')' + << ColumnBreak() << RowBreak(); +} + +void ConsoleReporter::testCaseEnded(TestCaseStats const& _testCaseStats) { + m_tablePrinter->close(); + StreamingReporterBase::testCaseEnded(_testCaseStats); + m_headerPrinted = false; +} +void ConsoleReporter::testRunEnded(TestRunStats const& _testRunStats) { + printTotalsDivider(_testRunStats.totals); + printTestRunTotals( m_stream, *m_colour, _testRunStats.totals ); + m_stream << '\n' << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); +} +void ConsoleReporter::testRunStarting(TestRunInfo const& _testRunInfo) { + StreamingReporterBase::testRunStarting(_testRunInfo); + if ( m_config->testSpec().hasFilters() ) { + m_stream << m_colour->guardColour( Colour::BrightYellow ) << "Filters: " + << m_config->testSpec() << '\n'; + } + m_stream << "Randomness seeded to: " << getSeed() << '\n'; +} + +void ConsoleReporter::lazyPrint() { + + m_tablePrinter->close(); + lazyPrintWithoutClosingBenchmarkTable(); +} + +void ConsoleReporter::lazyPrintWithoutClosingBenchmarkTable() { + + if ( !m_testRunInfoPrinted ) { + lazyPrintRunInfo(); + } + if (!m_headerPrinted) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } +} +void ConsoleReporter::lazyPrintRunInfo() { + m_stream << '\n' + << lineOfChars( '~' ) << '\n' + << m_colour->guardColour( Colour::SecondaryText ) + << currentTestRunInfo.name << " is a Catch2 v" << libraryVersion() + << " host application.\n" + << "Run with -? for options\n\n"; + + m_testRunInfoPrinted = true; +} +void ConsoleReporter::printTestCaseAndSectionHeader() { + assert(!m_sectionStack.empty()); + printOpenHeader(currentTestCaseInfo->name); + + if (m_sectionStack.size() > 1) { + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); + + auto + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(it->name, 2); + } + + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; + + + m_stream << lineOfChars( '-' ) << '\n' + << m_colour->guardColour( Colour::FileName ) << lineInfo << '\n' + << lineOfChars( '.' ) << "\n\n" + << std::flush; +} + +void ConsoleReporter::printClosedHeader(std::string const& _name) { + printOpenHeader(_name); + m_stream << lineOfChars('.') << '\n'; +} +void ConsoleReporter::printOpenHeader(std::string const& _name) { + m_stream << lineOfChars('-') << '\n'; + { + auto guard = m_colour->guardColour( Colour::Headers ).engage( m_stream ); + printHeaderString(_name); + } +} + +void ConsoleReporter::printHeaderString(std::string const& _string, std::size_t indent) { + // We want to get a bit fancy with line breaking here, so that subsequent + // lines start after ":" if one is present, e.g. + // ``` + // blablabla: Fancy + // linebreaking + // ``` + // but we also want to avoid problems with overly long indentation causing + // the text to take up too many lines, e.g. + // ``` + // blablabla: F + // a + // n + // c + // y + // . + // . + // . + // ``` + // So we limit the prefix indentation check to first quarter of the possible + // width + std::size_t idx = _string.find( ": " ); + if ( idx != std::string::npos && idx < CATCH_CONFIG_CONSOLE_WIDTH / 4 ) { + idx += 2; + } else { + idx = 0; + } + m_stream << TextFlow::Column( _string ) + .indent( indent + idx ) + .initialIndent( indent ) + << '\n'; +} + +void ConsoleReporter::printTotalsDivider(Totals const& totals) { + if (totals.testCases.total() > 0) { + std::size_t failedRatio = makeRatio(totals.testCases.failed, totals.testCases.total()); + std::size_t failedButOkRatio = makeRatio(totals.testCases.failedButOk, totals.testCases.total()); + std::size_t passedRatio = makeRatio(totals.testCases.passed, totals.testCases.total()); + std::size_t skippedRatio = makeRatio(totals.testCases.skipped, totals.testCases.total()); + while (failedRatio + failedButOkRatio + passedRatio + skippedRatio < CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)++; + while (failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH - 1) + findMax(failedRatio, failedButOkRatio, passedRatio, skippedRatio)--; + + m_stream << m_colour->guardColour( Colour::Error ) + << std::string( failedRatio, '=' ) + << m_colour->guardColour( Colour::ResultExpectedFailure ) + << std::string( failedButOkRatio, '=' ); + if ( totals.testCases.allPassed() ) { + m_stream << m_colour->guardColour( Colour::ResultSuccess ) + << std::string( passedRatio, '=' ); + } else { + m_stream << m_colour->guardColour( Colour::Success ) + << std::string( passedRatio, '=' ); + } + m_stream << m_colour->guardColour( Colour::Skip ) + << std::string( skippedRatio, '=' ); + } else { + m_stream << m_colour->guardColour( Colour::Warning ) + << std::string( CATCH_CONFIG_CONSOLE_WIDTH - 1, '=' ); + } + m_stream << '\n'; +} + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + + + +#include <algorithm> +#include <cassert> + +namespace Catch { + namespace { + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ): m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) = default; + bool operator()( + Detail::unique_ptr<CumulativeReporterBase::SectionNode> const& + node ) const { + return ( + ( node->stats.sectionInfo.name == m_other.name ) && + ( node->stats.sectionInfo.lineInfo == m_other.lineInfo ) ); + } + void operator=( BySectionInfo const& ) = delete; + + private: + SectionInfo const& m_other; + }; + + } // namespace + + namespace Detail { + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + AssertionStats const& assertion ): + m_assertion( assertion ) {} + + AssertionOrBenchmarkResult::AssertionOrBenchmarkResult( + BenchmarkStats<> const& benchmark ): + m_benchmark( benchmark ) {} + + bool AssertionOrBenchmarkResult::isAssertion() const { + return m_assertion.some(); + } + bool AssertionOrBenchmarkResult::isBenchmark() const { + return m_benchmark.some(); + } + + AssertionStats const& AssertionOrBenchmarkResult::asAssertion() const { + assert(m_assertion.some()); + + return *m_assertion; + } + BenchmarkStats<> const& AssertionOrBenchmarkResult::asBenchmark() const { + assert(m_benchmark.some()); + + return *m_benchmark; + } + + } + + CumulativeReporterBase::~CumulativeReporterBase() = default; + + void CumulativeReporterBase::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_sectionStack.back()->assertionsAndBenchmarks.emplace_back(benchmarkStats); + } + + void + CumulativeReporterBase::sectionStarting( SectionInfo const& sectionInfo ) { + // We need a copy, because SectionStats expect to take ownership + SectionStats incompleteStats( SectionInfo(sectionInfo), Counts(), 0, false ); + SectionNode* node; + if ( m_sectionStack.empty() ) { + if ( !m_rootSection ) { + m_rootSection = + Detail::make_unique<SectionNode>( incompleteStats ); + } + node = m_rootSection.get(); + } else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if ( it == parentNode.childSections.end() ) { + auto newNode = + Detail::make_unique<SectionNode>( incompleteStats ); + node = newNode.get(); + parentNode.childSections.push_back( CATCH_MOVE( newNode ) ); + } else { + node = it->get(); + } + } + + m_deepestSection = node; + m_sectionStack.push_back( node ); + } + + void CumulativeReporterBase::assertionEnded( + AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + if ( m_shouldStoreFailedAssertions && + !assertionStats.assertionResult.isOk() ) { + static_cast<void>( + assertionStats.assertionResult.getExpandedExpression() ); + } + if ( m_shouldStoreSuccesfulAssertions && + assertionStats.assertionResult.isOk() ) { + static_cast<void>( + assertionStats.assertionResult.getExpandedExpression() ); + } + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertionsAndBenchmarks.emplace_back( assertionStats ); + } + + void CumulativeReporterBase::sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + + void CumulativeReporterBase::testCaseEnded( + TestCaseStats const& testCaseStats ) { + auto node = Detail::make_unique<TestCaseNode>( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( CATCH_MOVE(m_rootSection) ); + m_testCases.push_back( CATCH_MOVE(node) ); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + + + void CumulativeReporterBase::testRunEnded( TestRunStats const& testRunStats ) { + assert(!m_testRun && "CumulativeReporterBase assumes there can only be one test run"); + m_testRun = Detail::make_unique<TestRunNode>( testRunStats ); + m_testRun->children.swap( m_testCases ); + testRunEndedCumulative(); + } + + bool CumulativeReporterBase::SectionNode::hasAnyAssertions() const { + return std::any_of( + assertionsAndBenchmarks.begin(), + assertionsAndBenchmarks.end(), + []( Detail::AssertionOrBenchmarkResult const& res ) { + return res.isAssertion(); + } ); + } + +} // end namespace Catch + + + + +namespace Catch { + + void EventListenerBase::fatalErrorEncountered( StringRef ) {} + + void EventListenerBase::benchmarkPreparing( StringRef ) {} + void EventListenerBase::benchmarkStarting( BenchmarkInfo const& ) {} + void EventListenerBase::benchmarkEnded( BenchmarkStats<> const& ) {} + void EventListenerBase::benchmarkFailed( StringRef ) {} + + void EventListenerBase::assertionStarting( AssertionInfo const& ) {} + + void EventListenerBase::assertionEnded( AssertionStats const& ) {} + void EventListenerBase::listReporters( + std::vector<ReporterDescription> const& ) {} + void EventListenerBase::listListeners( + std::vector<ListenerDescription> const& ) {} + void EventListenerBase::listTests( std::vector<TestCaseHandle> const& ) {} + void EventListenerBase::listTags( std::vector<TagInfo> const& ) {} + void EventListenerBase::noMatchingTestCases( StringRef ) {} + void EventListenerBase::reportInvalidTestSpec( StringRef ) {} + void EventListenerBase::testRunStarting( TestRunInfo const& ) {} + void EventListenerBase::testCaseStarting( TestCaseInfo const& ) {} + void EventListenerBase::testCasePartialStarting(TestCaseInfo const&, uint64_t) {} + void EventListenerBase::sectionStarting( SectionInfo const& ) {} + void EventListenerBase::sectionEnded( SectionStats const& ) {} + void EventListenerBase::testCasePartialEnded(TestCaseStats const&, uint64_t) {} + void EventListenerBase::testCaseEnded( TestCaseStats const& ) {} + void EventListenerBase::testRunEnded( TestRunStats const& ) {} + void EventListenerBase::skipTest( TestCaseInfo const& ) {} +} // namespace Catch + + + + +#include <algorithm> +#include <cfloat> +#include <cstdio> +#include <ostream> +#include <iomanip> + +namespace Catch { + + namespace { + void listTestNamesOnly(std::ostream& out, + std::vector<TestCaseHandle> const& tests) { + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + + if (startsWith(testCaseInfo.name, '#')) { + out << '"' << testCaseInfo.name << '"'; + } else { + out << testCaseInfo.name; + } + + out << '\n'; + } + out << std::flush; + } + } // end unnamed namespace + + + // Because formatting using c++ streams is stateful, drop down to C is + // required Alternatively we could use stringstream, but its performance + // is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + size_t printedLength = static_cast<size_t>( + sprintf_s( buffer, "%.3f", duration ) ); +#else + size_t printedLength = static_cast<size_t>( + std::snprintf( buffer, maxDoubleSize, "%.3f", duration ) ); +#endif + return std::string( buffer, printedLength ); + } + + bool shouldShowDuration( IConfig const& config, double duration ) { + if ( config.showDurations() == ShowDurations::Always ) { + return true; + } + if ( config.showDurations() == ShowDurations::Never ) { + return false; + } + const double min = config.minDuration(); + return min >= 0 && duration >= min; + } + + std::string serializeFilters( std::vector<std::string> const& filters ) { + // We add a ' ' separator between each filter + size_t serialized_size = filters.size() - 1; + for (auto const& filter : filters) { + serialized_size += filter.size(); + } + + std::string serialized; + serialized.reserve(serialized_size); + bool first = true; + + for (auto const& filter : filters) { + if (!first) { + serialized.push_back(' '); + } + first = false; + serialized.append(filter); + } + + return serialized; + } + + std::ostream& operator<<( std::ostream& out, lineOfChars value ) { + for ( size_t idx = 0; idx < CATCH_CONFIG_CONSOLE_WIDTH - 1; ++idx ) { + out.put( value.c ); + } + return out; + } + + void + defaultListReporters( std::ostream& out, + std::vector<ReporterDescription> const& descriptions, + Verbosity verbosity ) { + out << "Available reporters:\n"; + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ReporterDescription const& lhs, + ReporterDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + for ( auto const& desc : descriptions ) { + if ( verbosity == Verbosity::Quiet ) { + out << TextFlow::Column( desc.name ) + .indent( 2 ) + .width( 5 + maxNameLen ) + << '\n'; + } else { + out << TextFlow::Column( desc.name + ':' ) + .indent( 2 ) + .width( 5 + maxNameLen ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + } + out << '\n' << std::flush; + } + + void defaultListListeners( std::ostream& out, + std::vector<ListenerDescription> const& descriptions ) { + out << "Registered listeners:\n"; + + if(descriptions.empty()) { + return; + } + + const auto maxNameLen = + std::max_element( descriptions.begin(), + descriptions.end(), + []( ListenerDescription const& lhs, + ListenerDescription const& rhs ) { + return lhs.name.size() < rhs.name.size(); + } ) + ->name.size(); + + for ( auto const& desc : descriptions ) { + out << TextFlow::Column( static_cast<std::string>( desc.name ) + + ':' ) + .indent( 2 ) + .width( maxNameLen + 5 ) + + TextFlow::Column( desc.description ) + .initialIndent( 0 ) + .indent( 2 ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen - 8 ) + << '\n'; + } + + out << '\n' << std::flush; + } + + void defaultListTags( std::ostream& out, + std::vector<TagInfo> const& tags, + bool isFiltered ) { + if ( isFiltered ) { + out << "Tags for matching test cases:\n"; + } else { + out << "All available tags:\n"; + } + + for ( auto const& tagCount : tags ) { + ReusableStringStream rss; + rss << " " << std::setw( 2 ) << tagCount.count << " "; + auto str = rss.str(); + auto wrapper = TextFlow::Column( tagCount.all() ) + .initialIndent( 0 ) + .indent( str.size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH - 10 ); + out << str << wrapper << '\n'; + } + out << pluralise(tags.size(), "tag"_sr) << "\n\n" << std::flush; + } + + void defaultListTests(std::ostream& out, ColourImpl* streamColour, std::vector<TestCaseHandle> const& tests, bool isFiltered, Verbosity verbosity) { + // We special case this to provide the equivalent of old + // `--list-test-names-only`, which could then be used by the + // `--input-file` option. + if (verbosity == Verbosity::Quiet) { + listTestNamesOnly(out, tests); + return; + } + + if (isFiltered) { + out << "Matching test cases:\n"; + } else { + out << "All available test cases:\n"; + } + + for (auto const& test : tests) { + auto const& testCaseInfo = test.getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + auto colourGuard = streamColour->guardColour( colour ).engage( out ); + + out << TextFlow::Column(testCaseInfo.name).indent(2) << '\n'; + if (verbosity >= Verbosity::High) { + out << TextFlow::Column(Catch::Detail::stringify(testCaseInfo.lineInfo)).indent(4) << '\n'; + } + if (!testCaseInfo.tags.empty() && + verbosity > Verbosity::Quiet) { + out << TextFlow::Column(testCaseInfo.tagsAsString()).indent(6) << '\n'; + } + } + + if (isFiltered) { + out << pluralise(tests.size(), "matching test case"_sr); + } else { + out << pluralise(tests.size(), "test case"_sr); + } + out << "\n\n" << std::flush; + } + + namespace { + class SummaryColumn { + public: + SummaryColumn( std::string suffix, Colour::Code colour ): + m_suffix( CATCH_MOVE( suffix ) ), m_colour( colour ) {} + + SummaryColumn&& addRow( std::uint64_t count ) && { + std::string row = std::to_string(count); + auto const new_width = std::max( m_width, row.size() ); + if ( new_width > m_width ) { + for ( auto& oldRow : m_rows ) { + oldRow.insert( 0, new_width - m_width, ' ' ); + } + } else { + row.insert( 0, m_width - row.size(), ' ' ); + } + m_width = new_width; + m_rows.push_back( row ); + return std::move( *this ); + } + + std::string const& getSuffix() const { return m_suffix; } + Colour::Code getColour() const { return m_colour; } + std::string const& getRow( std::size_t index ) const { + return m_rows[index]; + } + + private: + std::string m_suffix; + Colour::Code m_colour; + std::size_t m_width = 0; + std::vector<std::string> m_rows; + }; + + void printSummaryRow( std::ostream& stream, + ColourImpl& colour, + StringRef label, + std::vector<SummaryColumn> const& cols, + std::size_t row ) { + for ( auto const& col : cols ) { + auto const& value = col.getRow( row ); + auto const& suffix = col.getSuffix(); + if ( suffix.empty() ) { + stream << label << ": "; + if ( value != "0" ) { + stream << value; + } else { + stream << colour.guardColour( Colour::Warning ) + << "- none -"; + } + } else if ( value != "0" ) { + stream << colour.guardColour( Colour::LightGrey ) << " | " + << colour.guardColour( col.getColour() ) << value + << ' ' << suffix; + } + } + stream << '\n'; + } + } // namespace + + void printTestRunTotals( std::ostream& stream, + ColourImpl& streamColour, + Totals const& totals ) { + if ( totals.testCases.total() == 0 ) { + stream << streamColour.guardColour( Colour::Warning ) + << "No tests ran\n"; + return; + } + + if ( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { + stream << streamColour.guardColour( Colour::ResultSuccess ) + << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion"_sr ) + << " in " + << pluralise( totals.testCases.passed, "test case"_sr ) + << ')' << '\n'; + return; + } + + std::vector<SummaryColumn> columns; + // Don't include "skipped assertions" in total count + const auto totalAssertionCount = + totals.assertions.total() - totals.assertions.skipped; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totalAssertionCount ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "skipped", Colour::Skip ) + .addRow( totals.testCases.skipped ) + // Don't print "skipped assertions" + .addRow( 0 ) ); + columns.push_back( + SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + printSummaryRow( stream, streamColour, "test cases"_sr, columns, 0 ); + printSummaryRow( stream, streamColour, "assertions"_sr, columns, 1 ); + } + +} // namespace Catch + + +// + +namespace Catch { + namespace { + void writeSourceInfo( JsonObjectWriter& writer, + SourceLineInfo const& sourceInfo ) { + auto source_location_writer = + writer.write( "source-location"_sr ).writeObject(); + source_location_writer.write( "filename"_sr ) + .write( sourceInfo.file ); + source_location_writer.write( "line"_sr ).write( sourceInfo.line ); + } + + void writeTags( JsonArrayWriter writer, std::vector<Tag> const& tags ) { + for ( auto const& tag : tags ) { + writer.write( tag.original ); + } + } + + void writeProperties( JsonArrayWriter writer, + TestCaseInfo const& info ) { + if ( info.isHidden() ) { writer.write( "is-hidden"_sr ); } + if ( info.okToFail() ) { writer.write( "ok-to-fail"_sr ); } + if ( info.expectedToFail() ) { + writer.write( "expected-to-fail"_sr ); + } + if ( info.throws() ) { writer.write( "throws"_sr ); } + } + + } // namespace + + JsonReporter::JsonReporter( ReporterConfig&& config ): + StreamingReporterBase{ CATCH_MOVE( config ) } { + + m_preferences.shouldRedirectStdOut = true; + // TBD: Do we want to report all assertions? XML reporter does + // not, but for machine-parseable reporters I think the answer + // should be yes. + m_preferences.shouldReportAllAssertions = true; + + m_objectWriters.emplace( m_stream ); + m_writers.emplace( Writer::Object ); + auto& writer = m_objectWriters.top(); + + writer.write( "version"_sr ).write( 1 ); + + { + auto metadata_writer = writer.write( "metadata"_sr ).writeObject(); + metadata_writer.write( "name"_sr ).write( m_config->name() ); + metadata_writer.write( "rng-seed"_sr ).write( m_config->rngSeed() ); + metadata_writer.write( "catch2-version"_sr ) + .write( libraryVersion() ); + if ( m_config->testSpec().hasFilters() ) { + metadata_writer.write( "filters"_sr ) + .write( m_config->testSpec() ); + } + } + } + + JsonReporter::~JsonReporter() { + endListing(); + // TODO: Ensure this closes the top level object, add asserts + assert( m_writers.size() == 1 && "Only the top level object should be open" ); + assert( m_writers.top() == Writer::Object ); + endObject(); + m_stream << '\n' << std::flush; + assert( m_writers.empty() ); + } + + JsonArrayWriter& JsonReporter::startArray() { + m_arrayWriters.emplace( m_arrayWriters.top().writeArray() ); + m_writers.emplace( Writer::Array ); + return m_arrayWriters.top(); + } + JsonArrayWriter& JsonReporter::startArray( StringRef key ) { + m_arrayWriters.emplace( + m_objectWriters.top().write( key ).writeArray() ); + m_writers.emplace( Writer::Array ); + return m_arrayWriters.top(); + } + + JsonObjectWriter& JsonReporter::startObject() { + m_objectWriters.emplace( m_arrayWriters.top().writeObject() ); + m_writers.emplace( Writer::Object ); + return m_objectWriters.top(); + } + JsonObjectWriter& JsonReporter::startObject( StringRef key ) { + m_objectWriters.emplace( + m_objectWriters.top().write( key ).writeObject() ); + m_writers.emplace( Writer::Object ); + return m_objectWriters.top(); + } + + void JsonReporter::endObject() { + assert( isInside( Writer::Object ) ); + m_objectWriters.pop(); + m_writers.pop(); + } + void JsonReporter::endArray() { + assert( isInside( Writer::Array ) ); + m_arrayWriters.pop(); + m_writers.pop(); + } + + bool JsonReporter::isInside( Writer writer ) { + return !m_writers.empty() && m_writers.top() == writer; + } + + void JsonReporter::startListing() { + if ( !m_startedListing ) { startObject( "listings"_sr ); } + m_startedListing = true; + } + void JsonReporter::endListing() { + if ( m_startedListing ) { endObject(); } + m_startedListing = false; + } + + std::string JsonReporter::getDescription() { + return "Outputs listings as JSON. Test listing is Work-in-Progress!"; + } + + void JsonReporter::testRunStarting( TestRunInfo const& runInfo ) { + StreamingReporterBase::testRunStarting( runInfo ); + endListing(); + + assert( isInside( Writer::Object ) ); + startObject( "test-run"_sr ); + startArray( "test-cases"_sr ); + } + + static void writeCounts( JsonObjectWriter&& writer, Counts const& counts ) { + writer.write( "passed"_sr ).write( counts.passed ); + writer.write( "failed"_sr ).write( counts.failed ); + writer.write( "fail-but-ok"_sr ).write( counts.failedButOk ); + writer.write( "skipped"_sr ).write( counts.skipped ); + } + + void JsonReporter::testRunEnded(TestRunStats const& runStats) { + assert( isInside( Writer::Array ) ); + // End "test-cases" + endArray(); + + { + auto totals = + m_objectWriters.top().write( "totals"_sr ).writeObject(); + writeCounts( totals.write( "assertions"_sr ).writeObject(), + runStats.totals.assertions ); + writeCounts( totals.write( "test-cases"_sr ).writeObject(), + runStats.totals.testCases ); + } + + // End the "test-run" object + endObject(); + } + + void JsonReporter::testCaseStarting( TestCaseInfo const& tcInfo ) { + StreamingReporterBase::testCaseStarting( tcInfo ); + + assert( isInside( Writer::Array ) && + "We should be in the 'test-cases' array" ); + startObject(); + // "test-info" prelude + { + auto testInfo = + m_objectWriters.top().write( "test-info"_sr ).writeObject(); + // TODO: handle testName vs className!! + testInfo.write( "name"_sr ).write( tcInfo.name ); + writeSourceInfo(testInfo, tcInfo.lineInfo); + writeTags( testInfo.write( "tags"_sr ).writeArray(), tcInfo.tags ); + writeProperties( testInfo.write( "properties"_sr ).writeArray(), + tcInfo ); + } + + + // Start the array for individual test runs (testCasePartial pairs) + startArray( "runs"_sr ); + } + + void JsonReporter::testCaseEnded( TestCaseStats const& tcStats ) { + StreamingReporterBase::testCaseEnded( tcStats ); + + // We need to close the 'runs' array before finishing the test case + assert( isInside( Writer::Array ) ); + endArray(); + + { + auto totals = + m_objectWriters.top().write( "totals"_sr ).writeObject(); + writeCounts( totals.write( "assertions"_sr ).writeObject(), + tcStats.totals.assertions ); + // We do not write the test case totals, because there will always be just one test case here. + // TODO: overall "result" -> success, skip, fail here? Or in partial result? + } + // We do not write out stderr/stdout, because we instead wrote those out in partial runs + + // TODO: aborting? + + // And we also close this test case's object + assert( isInside( Writer::Object ) ); + endObject(); + } + + void JsonReporter::testCasePartialStarting( TestCaseInfo const& /*tcInfo*/, + uint64_t index ) { + startObject(); + m_objectWriters.top().write( "run-idx"_sr ).write( index ); + startArray( "path"_sr ); + // TODO: we want to delay most of the printing to the 'root' section + // TODO: childSection key name? + } + + void JsonReporter::testCasePartialEnded( TestCaseStats const& tcStats, + uint64_t /*index*/ ) { + // Fixme: the top level section handles this. + //// path object + endArray(); + if ( !tcStats.stdOut.empty() ) { + m_objectWriters.top() + .write( "captured-stdout"_sr ) + .write( tcStats.stdOut ); + } + if ( !tcStats.stdErr.empty() ) { + m_objectWriters.top() + .write( "captured-stderr"_sr ) + .write( tcStats.stdErr ); + } + { + auto totals = + m_objectWriters.top().write( "totals"_sr ).writeObject(); + writeCounts( totals.write( "assertions"_sr ).writeObject(), + tcStats.totals.assertions ); + // We do not write the test case totals, because there will + // always be just one test case here. + // TODO: overall "result" -> success, skip, fail here? Or in + // partial result? + } + // TODO: aborting? + // run object + endObject(); + } + + void JsonReporter::sectionStarting( SectionInfo const& sectionInfo ) { + assert( isInside( Writer::Array ) && + "Section should always start inside an object" ); + // We want to nest top level sections, even though it shares name + // and source loc with the TEST_CASE + auto& sectionObject = startObject(); + sectionObject.write( "kind"_sr ).write( "section"_sr ); + sectionObject.write( "name"_sr ).write( sectionInfo.name ); + writeSourceInfo( m_objectWriters.top(), sectionInfo.lineInfo ); + + + // TBD: Do we want to create this event lazily? It would become + // rather complex, but we could do it, and it would look + // better for empty sections. OTOH, empty sections should + // be rare. + startArray( "path"_sr ); + } + void JsonReporter::sectionEnded( SectionStats const& /*sectionStats */) { + // End the subpath array + endArray(); + // TODO: metadata + // TODO: what info do we have here? + + // End the section object + endObject(); + } + + void JsonReporter::assertionStarting( AssertionInfo const& /*assertionInfo*/ ) {} + void JsonReporter::assertionEnded( AssertionStats const& assertionStats ) { + // TODO: There is lot of different things to handle here, but + // we can fill it in later, after we show that the basic + // outline and streaming reporter impl works well enough. + //if ( !m_config->includeSuccessfulResults() + // && assertionStats.assertionResult.isOk() ) { + // return; + //} + assert( isInside( Writer::Array ) ); + auto assertionObject = m_arrayWriters.top().writeObject(); + + assertionObject.write( "kind"_sr ).write( "assertion"_sr ); + writeSourceInfo( assertionObject, + assertionStats.assertionResult.getSourceInfo() ); + assertionObject.write( "status"_sr ) + .write( assertionStats.assertionResult.isOk() ); + // TODO: handling of result. + // TODO: messages + // TODO: totals? + } + + + void JsonReporter::benchmarkPreparing( StringRef name ) { (void)name; } + void JsonReporter::benchmarkStarting( BenchmarkInfo const& ) {} + void JsonReporter::benchmarkEnded( BenchmarkStats<> const& ) {} + void JsonReporter::benchmarkFailed( StringRef error ) { (void)error; } + + void JsonReporter::listReporters( + std::vector<ReporterDescription> const& descriptions ) { + startListing(); + + auto writer = + m_objectWriters.top().write( "reporters"_sr ).writeArray(); + for ( auto const& desc : descriptions ) { + auto desc_writer = writer.writeObject(); + desc_writer.write( "name"_sr ).write( desc.name ); + desc_writer.write( "description"_sr ).write( desc.description ); + } + } + void JsonReporter::listListeners( + std::vector<ListenerDescription> const& descriptions ) { + startListing(); + + auto writer = + m_objectWriters.top().write( "listeners"_sr ).writeArray(); + + for ( auto const& desc : descriptions ) { + auto desc_writer = writer.writeObject(); + desc_writer.write( "name"_sr ).write( desc.name ); + desc_writer.write( "description"_sr ).write( desc.description ); + } + } + void JsonReporter::listTests( std::vector<TestCaseHandle> const& tests ) { + startListing(); + + auto writer = m_objectWriters.top().write( "tests"_sr ).writeArray(); + + for ( auto const& test : tests ) { + auto desc_writer = writer.writeObject(); + auto const& info = test.getTestCaseInfo(); + + desc_writer.write( "name"_sr ).write( info.name ); + desc_writer.write( "class-name"_sr ).write( info.className ); + { + auto tag_writer = desc_writer.write( "tags"_sr ).writeArray(); + for ( auto const& tag : info.tags ) { + tag_writer.write( tag.original ); + } + } + writeSourceInfo( desc_writer, info.lineInfo ); + } + } + void JsonReporter::listTags( std::vector<TagInfo> const& tags ) { + startListing(); + + auto writer = m_objectWriters.top().write( "tags"_sr ).writeArray(); + for ( auto const& tag : tags ) { + auto tag_writer = writer.writeObject(); + { + auto aliases_writer = + tag_writer.write( "aliases"_sr ).writeArray(); + for ( auto alias : tag.spellings ) { + aliases_writer.write( alias ); + } + } + tag_writer.write( "count"_sr ).write( tag.count ); + } + } +} // namespace Catch + + + + +#include <cassert> +#include <ctime> +#include <algorithm> +#include <iomanip> + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + time_t rawtime; + std::time(&rawtime); + + std::tm timeInfo = {}; +#if defined (_MSC_VER) || defined (__MINGW32__) + gmtime_s(&timeInfo, &rawtime); +#elif defined (CATCH_PLATFORM_PLAYSTATION) + gmtime_s(&rawtime, &timeInfo); +#elif defined (__IAR_SYSTEMS_ICC__) + timeInfo = *std::gmtime(&rawtime); +#else + gmtime_r(&rawtime, &timeInfo); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + + return std::string(timeStamp, timeStampSize - 1); + } + + std::string fileNameTag(std::vector<Tag> const& tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (Tag const& tag) { + return tag.original.size() > 0 + && tag.original[0] == '#'; }); + if (it != tags.end()) { + return static_cast<std::string>( + it->original.substr(1, it->original.size() - 1) + ); + } + return std::string(); + } + + // Formats the duration in seconds to 3 decimal places. + // This is done because some genius defined Maven Surefire schema + // in a way that only accepts 3 decimal places, and tools like + // Jenkins use that schema for validation JUnit reporter output. + std::string formatDuration( double seconds ) { + ReusableStringStream rss; + rss << std::fixed << std::setprecision( 3 ) << seconds; + return rss.str(); + } + + static void normalizeNamespaceMarkers(std::string& str) { + std::size_t pos = str.find( "::" ); + while ( pos != std::string::npos ) { + str.replace( pos, 2, "." ); + pos += 1; + pos = str.find( "::", pos ); + } + } + + } // anonymous namespace + + JunitReporter::JunitReporter( ReporterConfig&& _config ) + : CumulativeReporterBase( CATCH_MOVE(_config) ), + xml( m_stream ) + { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = false; + m_shouldStoreSuccesfulAssertions = false; + } + + std::string JunitReporter::getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + void JunitReporter::testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + suiteTimer.start(); + stdOutForSuite.clear(); + stdErrForSuite.clear(); + unexpectedExceptions = 0; + } + + void JunitReporter::testCaseStarting( TestCaseInfo const& testCaseInfo ) { + m_okToFail = testCaseInfo.okToFail(); + } + + void JunitReporter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + CumulativeReporterBase::assertionEnded( assertionStats ); + } + + void JunitReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite += testCaseStats.stdOut; + stdErrForSuite += testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + void JunitReporter::testRunEndedCumulative() { + const auto suiteTime = suiteTimer.getElapsedSeconds(); + writeRun( *m_testRun, suiteTime ); + xml.endElement(); + } + + void JunitReporter::writeRun( TestRunNode const& testRunNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + + TestRunStats const& stats = testRunNode.value; + xml.writeAttribute( "name"_sr, stats.runInfo.name ); + xml.writeAttribute( "errors"_sr, unexpectedExceptions ); + xml.writeAttribute( "failures"_sr, stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "skipped"_sr, stats.totals.assertions.skipped ); + xml.writeAttribute( "tests"_sr, stats.totals.assertions.total() ); + xml.writeAttribute( "hostname"_sr, "tbd"_sr ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time"_sr, ""_sr ); + else + xml.writeAttribute( "time"_sr, formatDuration( suiteTime ) ); + xml.writeAttribute( "timestamp"_sr, getCurrentTimestamp() ); + + // Write properties + { + auto properties = xml.scopedElement("properties"); + xml.scopedElement("property") + .writeAttribute("name"_sr, "random-seed"_sr) + .writeAttribute("value"_sr, m_config->rngSeed()); + if (m_config->testSpec().hasFilters()) { + xml.scopedElement("property") + .writeAttribute("name"_sr, "filters"_sr) + .writeAttribute("value"_sr, m_config->testSpec()); + } + } + + // Write test cases + for( auto const& child : testRunNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); + } + + void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = + static_cast<std::string>( stats.testInfo->className ); + + if( className.empty() ) { + className = fileNameTag(stats.testInfo->tags); + if ( className.empty() ) { + className = "global"; + } + } + + if ( !m_config->name().empty() ) + className = static_cast<std::string>(m_config->name()) + '.' + className; + + normalizeNamespaceMarkers(className); + + writeSection( className, "", rootSection, stats.testInfo->okToFail() ); + } + + void JunitReporter::writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; + + if ( sectionNode.stats.assertions.total() > 0 + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname"_sr, name ); + xml.writeAttribute( "name"_sr, "root"_sr ); + } + else { + xml.writeAttribute( "classname"_sr, className ); + xml.writeAttribute( "name"_sr, name ); + } + xml.writeAttribute( "time"_sr, formatDuration( sectionNode.stats.durationInSeconds ) ); + // This is not ideal, but it should be enough to mimic gtest's + // junit output. + // Ideally the JUnit reporter would also handle `skipTest` + // events and write those out appropriately. + xml.writeAttribute( "status"_sr, "run"_sr ); + + if (sectionNode.stats.assertions.failedButOk) { + xml.scopedElement("skipped") + .writeAttribute("message", "TEST_CASE tagged with !mayfail"); + } + + writeAssertions( sectionNode ); + + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode, testOkToFail ); + else + writeSection( className, name, *childNode, testOkToFail ); + } + + void JunitReporter::writeAssertions( SectionNode const& sectionNode ) { + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion()); + } + } + } + + void JunitReporter::writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if ( !result.isOk() || + result.getResultType() == ResultWas::ExplicitSkip ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + case ResultWas::ExplicitSkip: + elementName = "skipped"; + break; + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message"_sr, result.getExpression() ); + xml.writeAttribute( "type"_sr, result.getTestMacroName() ); + + ReusableStringStream rss; + if ( result.getResultType() == ResultWas::ExplicitSkip ) { + rss << "SKIPPED\n"; + } else { + rss << "FAILED" << ":\n"; + if (result.hasExpression()) { + rss << " "; + rss << result.getExpressionInMacro(); + rss << '\n'; + } + if (result.hasExpandedExpression()) { + rss << "with expansion:\n"; + rss << TextFlow::Column(result.getExpandedExpression()).indent(2) << '\n'; + } + } + + if( result.hasMessage() ) + rss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + rss << msg.message << '\n'; + + rss << "at " << result.getSourceInfo(); + xml.writeText( rss.str(), XmlFormatting::Newline ); + } + } + +} // end namespace Catch + + + + +#include <ostream> + +namespace Catch { + void MultiReporter::updatePreferences(IEventListener const& reporterish) { + m_preferences.shouldRedirectStdOut |= + reporterish.getPreferences().shouldRedirectStdOut; + m_preferences.shouldReportAllAssertions |= + reporterish.getPreferences().shouldReportAllAssertions; + } + + void MultiReporter::addListener( IEventListenerPtr&& listener ) { + updatePreferences(*listener); + m_reporterLikes.insert(m_reporterLikes.begin() + m_insertedListeners, CATCH_MOVE(listener) ); + ++m_insertedListeners; + } + + void MultiReporter::addReporter( IEventListenerPtr&& reporter ) { + updatePreferences(*reporter); + + // We will need to output the captured stdout if there are reporters + // that do not want it captured. + // We do not consider listeners, because it is generally assumed that + // listeners are output-transparent, even though they can ask for stdout + // capture to do something with it. + m_haveNoncapturingReporters |= !reporter->getPreferences().shouldRedirectStdOut; + + // Reporters can always be placed to the back without breaking the + // reporting order + m_reporterLikes.push_back( CATCH_MOVE( reporter ) ); + } + + void MultiReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->noMatchingTestCases( unmatchedSpec ); + } + } + + void MultiReporter::fatalErrorEncountered( StringRef error ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->fatalErrorEncountered( error ); + } + } + + void MultiReporter::reportInvalidTestSpec( StringRef arg ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->reportInvalidTestSpec( arg ); + } + } + + void MultiReporter::benchmarkPreparing( StringRef name ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkPreparing(name); + } + } + void MultiReporter::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkStarting( benchmarkInfo ); + } + } + void MultiReporter::benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->benchmarkEnded( benchmarkStats ); + } + } + + void MultiReporter::benchmarkFailed( StringRef error ) { + for (auto& reporterish : m_reporterLikes) { + reporterish->benchmarkFailed(error); + } + } + + void MultiReporter::testRunStarting( TestRunInfo const& testRunInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunStarting( testRunInfo ); + } + } + + void MultiReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseStarting( testInfo ); + } + } + + void + MultiReporter::testCasePartialStarting( TestCaseInfo const& testInfo, + uint64_t partNumber ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialStarting( testInfo, partNumber ); + } + } + + void MultiReporter::sectionStarting( SectionInfo const& sectionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionStarting( sectionInfo ); + } + } + + void MultiReporter::assertionStarting( AssertionInfo const& assertionInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->assertionStarting( assertionInfo ); + } + } + + void MultiReporter::assertionEnded( AssertionStats const& assertionStats ) { + const bool reportByDefault = + assertionStats.assertionResult.getResultType() != ResultWas::Ok || + m_config->includeSuccessfulResults(); + + for ( auto & reporterish : m_reporterLikes ) { + if ( reportByDefault || + reporterish->getPreferences().shouldReportAllAssertions ) { + reporterish->assertionEnded( assertionStats ); + } + } + } + + void MultiReporter::sectionEnded( SectionStats const& sectionStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->sectionEnded( sectionStats ); + } + } + + void MultiReporter::testCasePartialEnded( TestCaseStats const& testStats, + uint64_t partNumber ) { + if ( m_preferences.shouldRedirectStdOut && + m_haveNoncapturingReporters ) { + if ( !testStats.stdOut.empty() ) { + Catch::cout() << testStats.stdOut << std::flush; + } + if ( !testStats.stdErr.empty() ) { + Catch::cerr() << testStats.stdErr << std::flush; + } + } + + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCasePartialEnded( testStats, partNumber ); + } + } + + void MultiReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testCaseEnded( testCaseStats ); + } + } + + void MultiReporter::testRunEnded( TestRunStats const& testRunStats ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->testRunEnded( testRunStats ); + } + } + + + void MultiReporter::skipTest( TestCaseInfo const& testInfo ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->skipTest( testInfo ); + } + } + + void MultiReporter::listReporters(std::vector<ReporterDescription> const& descriptions) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listReporters(descriptions); + } + } + + void MultiReporter::listListeners( + std::vector<ListenerDescription> const& descriptions ) { + for ( auto& reporterish : m_reporterLikes ) { + reporterish->listListeners( descriptions ); + } + } + + void MultiReporter::listTests(std::vector<TestCaseHandle> const& tests) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTests(tests); + } + } + + void MultiReporter::listTags(std::vector<TagInfo> const& tags) { + for (auto& reporterish : m_reporterLikes) { + reporterish->listTags(tags); + } + } + +} // end namespace Catch + + + + + +namespace Catch { + namespace Detail { + + void registerReporterImpl( std::string const& name, + IReporterFactoryPtr reporterPtr ) { + CATCH_TRY { + getMutableRegistryHub().registerReporter( + name, CATCH_MOVE( reporterPtr ) ); + } + CATCH_CATCH_ALL { + // Do not throw when constructing global objects, instead + // register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + void registerListenerImpl( Detail::unique_ptr<EventListenerFactory> listenerFactory ) { + getMutableRegistryHub().registerListener( CATCH_MOVE(listenerFactory) ); + } + + + } // namespace Detail +} // namespace Catch + + + + +#include <map> + +namespace Catch { + + namespace { + std::string createMetadataString(IConfig const& config) { + ReusableStringStream sstr; + if ( config.testSpec().hasFilters() ) { + sstr << "filters='" + << config.testSpec() + << "' "; + } + sstr << "rng-seed=" << config.rngSeed(); + return sstr.str(); + } + } + + void SonarQubeReporter::testRunStarting(TestRunInfo const& testRunInfo) { + CumulativeReporterBase::testRunStarting(testRunInfo); + + xml.writeComment( createMetadataString( *m_config ) ); + xml.startElement("testExecutions"); + xml.writeAttribute("version"_sr, '1'); + } + + void SonarQubeReporter::writeRun( TestRunNode const& runNode ) { + std::map<StringRef, std::vector<TestCaseNode const*>> testsPerFile; + + for ( auto const& child : runNode.children ) { + testsPerFile[child->value.testInfo->lineInfo.file].push_back( + child.get() ); + } + + for ( auto const& kv : testsPerFile ) { + writeTestFile( kv.first, kv.second ); + } + } + + void SonarQubeReporter::writeTestFile(StringRef filename, std::vector<TestCaseNode const*> const& testCaseNodes) { + XmlWriter::ScopedElement e = xml.scopedElement("file"); + xml.writeAttribute("path"_sr, filename); + + for (auto const& child : testCaseNodes) + writeTestCase(*child); + } + + void SonarQubeReporter::writeTestCase(TestCaseNode const& testCaseNode) { + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert(testCaseNode.children.size() == 1); + SectionNode const& rootSection = *testCaseNode.children.front(); + writeSection("", rootSection, testCaseNode.value.testInfo->okToFail()); + } + + void SonarQubeReporter::writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail) { + std::string name = trim(sectionNode.stats.sectionInfo.name); + if (!rootName.empty()) + name = rootName + '/' + name; + + if ( sectionNode.stats.assertions.total() > 0 + || !sectionNode.stdOut.empty() + || !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement("testCase"); + xml.writeAttribute("name"_sr, name); + xml.writeAttribute("duration"_sr, static_cast<long>(sectionNode.stats.durationInSeconds * 1000)); + + writeAssertions(sectionNode, okToFail); + } + + for (auto const& childNode : sectionNode.childSections) + writeSection(name, *childNode, okToFail); + } + + void SonarQubeReporter::writeAssertions(SectionNode const& sectionNode, bool okToFail) { + for (auto const& assertionOrBenchmark : sectionNode.assertionsAndBenchmarks) { + if (assertionOrBenchmark.isAssertion()) { + writeAssertion(assertionOrBenchmark.asAssertion(), okToFail); + } + } + } + + void SonarQubeReporter::writeAssertion(AssertionStats const& stats, bool okToFail) { + AssertionResult const& result = stats.assertionResult; + if ( !result.isOk() || + result.getResultType() == ResultWas::ExplicitSkip ) { + std::string elementName; + if (okToFail) { + elementName = "skipped"; + } else { + switch (result.getResultType()) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + case ResultWas::ExpressionFailed: + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + case ResultWas::ExplicitSkip: + elementName = "skipped"; + break; + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + } + + XmlWriter::ScopedElement e = xml.scopedElement(elementName); + + ReusableStringStream messageRss; + messageRss << result.getTestMacroName() << '(' << result.getExpression() << ')'; + xml.writeAttribute("message"_sr, messageRss.str()); + + ReusableStringStream textRss; + if ( result.getResultType() == ResultWas::ExplicitSkip ) { + textRss << "SKIPPED\n"; + } else { + textRss << "FAILED:\n"; + if (result.hasExpression()) { + textRss << '\t' << result.getExpressionInMacro() << '\n'; + } + if (result.hasExpandedExpression()) { + textRss << "with expansion:\n\t" << result.getExpandedExpression() << '\n'; + } + } + + if (result.hasMessage()) + textRss << result.getMessage() << '\n'; + + for (auto const& msg : stats.infoMessages) + if (msg.type == ResultWas::Info) + textRss << msg.message << '\n'; + + textRss << "at " << result.getSourceInfo(); + xml.writeText(textRss.str(), XmlFormatting::Newline); + } + } + +} // end namespace Catch + + + +namespace Catch { + + StreamingReporterBase::~StreamingReporterBase() = default; + + void + StreamingReporterBase::testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + + void StreamingReporterBase::testRunEnded( TestRunStats const& ) { + currentTestCaseInfo = nullptr; + } + +} // end namespace Catch + + + +#include <algorithm> +#include <ostream> + +namespace Catch { + + namespace { + // Yes, this has to be outside the class and namespaced by naming. + // Making older compiler happy is hard. + static constexpr StringRef tapFailedString = "not ok"_sr; + static constexpr StringRef tapPassedString = "ok"_sr; + static constexpr Colour::Code tapDimColour = Colour::FileName; + + class TapAssertionPrinter { + public: + TapAssertionPrinter& operator= (TapAssertionPrinter const&) = delete; + TapAssertionPrinter(TapAssertionPrinter const&) = delete; + TapAssertionPrinter(std::ostream& _stream, AssertionStats const& _stats, std::size_t _counter, ColourImpl* colour_) + : stream(_stream) + , result(_stats.assertionResult) + , messages(_stats.infoMessages) + , itMessage(_stats.infoMessages.begin()) + , printInfoMessages(true) + , counter(_counter) + , colourImpl( colour_ ) {} + + void print() { + itMessage = messages.begin(); + + switch (result.getResultType()) { + case ResultWas::Ok: + printResultType(tapPassedString); + printOriginalExpression(); + printReconstructedExpression(); + if (!result.hasExpression()) + printRemainingMessages(Colour::None); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if (result.isOk()) { + printResultType(tapPassedString); + } else { + printResultType(tapFailedString); + } + printOriginalExpression(); + printReconstructedExpression(); + if (result.isOk()) { + printIssue(" # TODO"); + } + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType(tapFailedString); + printIssue("unexpected exception with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType(tapFailedString); + printIssue("fatal error condition with message:"_sr); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType(tapFailedString); + printIssue("expected exception, got none"_sr); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType("info"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType("warning"_sr); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType(tapFailedString); + printIssue("explicitly"_sr); + printRemainingMessages(Colour::None); + break; + case ResultWas::ExplicitSkip: + printResultType(tapPassedString); + printIssue(" # SKIP"_sr); + printMessage(); + printRemainingMessages(); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType("** internal error **"_sr); + break; + } + } + + private: + void printResultType(StringRef passOrFail) const { + if (!passOrFail.empty()) { + stream << passOrFail << ' ' << counter << " -"; + } + } + + void printIssue(StringRef issue) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if (result.hasExpression()) { + stream << ';'; + stream << colourImpl->guardColour( tapDimColour ) + << " expression was:"; + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if (result.hasExpression()) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if (result.hasExpandedExpression()) { + stream << colourImpl->guardColour( tapDimColour ) << " for: "; + + std::string expr = result.getExpandedExpression(); + std::replace(expr.begin(), expr.end(), '\n', ' '); + stream << expr; + } + } + + void printMessage() { + if (itMessage != messages.end()) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } + } + + void printRemainingMessages(Colour::Code colour = tapDimColour) { + if (itMessage == messages.end()) { + return; + } + + // using messages.end() directly (or auto) yields compilation error: + std::vector<MessageInfo>::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast<std::size_t>(itEnd - itMessage); + + stream << colourImpl->guardColour( colour ) << " with " + << pluralise( N, "message"_sr ) << ':'; + + for (; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if (printInfoMessages || itMessage->type != ResultWas::Info) { + stream << " '" << itMessage->message << '\''; + if (++itMessage != itEnd) { + stream << colourImpl->guardColour(tapDimColour) << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> const& messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; + std::size_t counter; + ColourImpl* colourImpl; + }; + + } // End anonymous namespace + + void TAPReporter::testRunStarting( TestRunInfo const& ) { + if ( m_config->testSpec().hasFilters() ) { + m_stream << "# filters: " << m_config->testSpec() << '\n'; + } + m_stream << "# rng-seed: " << m_config->rngSeed() << '\n'; + } + + void TAPReporter::noMatchingTestCases( StringRef unmatchedSpec ) { + m_stream << "# No test cases matched '" << unmatchedSpec << "'\n"; + } + + void TAPReporter::assertionEnded(AssertionStats const& _assertionStats) { + ++counter; + + m_stream << "# " << currentTestCaseInfo->name << '\n'; + TapAssertionPrinter printer(m_stream, _assertionStats, counter, m_colour.get()); + printer.print(); + + m_stream << '\n' << std::flush; + } + + void TAPReporter::testRunEnded(TestRunStats const& _testRunStats) { + m_stream << "1.." << _testRunStats.totals.assertions.total(); + if (_testRunStats.totals.testCases.total() == 0) { + m_stream << " # Skipped: No tests ran."; + } + m_stream << "\n\n" << std::flush; + StreamingReporterBase::testRunEnded(_testRunStats); + } + + + + +} // end namespace Catch + + + + +#include <cassert> +#include <ostream> + +namespace Catch { + + namespace { + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::ostream& os, std::string const& _string, std::size_t indent = 0) { + std::size_t i = _string.find(": "); + if (i != std::string::npos) + i += 2; + else + i = 0; + os << TextFlow::Column(_string) + .indent(indent + i) + .initialIndent(indent) << '\n'; + } + + std::string escape(StringRef str) { + std::string escaped = static_cast<std::string>(str); + replaceInPlace(escaped, "|", "||"); + replaceInPlace(escaped, "'", "|'"); + replaceInPlace(escaped, "\n", "|n"); + replaceInPlace(escaped, "\r", "|r"); + replaceInPlace(escaped, "[", "|["); + replaceInPlace(escaped, "]", "|]"); + return escaped; + } + } // end anonymous namespace + + + TeamCityReporter::~TeamCityReporter() = default; + + void TeamCityReporter::testRunStarting( TestRunInfo const& runInfo ) { + m_stream << "##teamcity[testSuiteStarted name='" << escape( runInfo.name ) + << "']\n"; + } + + void TeamCityReporter::testRunEnded( TestRunStats const& runStats ) { + m_stream << "##teamcity[testSuiteFinished name='" + << escape( runStats.runInfo.name ) << "']\n"; + } + + void TeamCityReporter::assertionEnded(AssertionStats const& assertionStats) { + AssertionResult const& result = assertionStats.assertionResult; + if ( !result.isOk() || + result.getResultType() == ResultWas::ExplicitSkip ) { + + ReusableStringStream msg; + if (!m_headerPrintedForThisSection) + printSectionHeader(msg.get()); + m_headerPrintedForThisSection = true; + + msg << result.getSourceInfo() << '\n'; + + switch (result.getResultType()) { + case ResultWas::ExpressionFailed: + msg << "expression failed"; + break; + case ResultWas::ThrewException: + msg << "unexpected exception"; + break; + case ResultWas::FatalErrorCondition: + msg << "fatal error condition"; + break; + case ResultWas::DidntThrowException: + msg << "no exception was thrown where one was expected"; + break; + case ResultWas::ExplicitFailure: + msg << "explicit failure"; + break; + case ResultWas::ExplicitSkip: + msg << "explicit skip"; + break; + + // We shouldn't get here because of the isOk() test + case ResultWas::Ok: + case ResultWas::Info: + case ResultWas::Warning: + CATCH_ERROR("Internal error in TeamCity reporter"); + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + CATCH_ERROR("Not implemented"); + } + if (assertionStats.infoMessages.size() == 1) + msg << " with message:"; + if (assertionStats.infoMessages.size() > 1) + msg << " with messages:"; + for (auto const& messageInfo : assertionStats.infoMessages) + msg << "\n \"" << messageInfo.message << '"'; + + + if (result.hasExpression()) { + msg << + "\n " << result.getExpressionInMacro() << "\n" + "with expansion:\n" + " " << result.getExpandedExpression() << '\n'; + } + + if ( result.getResultType() == ResultWas::ExplicitSkip ) { + m_stream << "##teamcity[testIgnored"; + } else if ( currentTestCaseInfo->okToFail() ) { + msg << "- failure ignore as test marked as 'ok to fail'\n"; + m_stream << "##teamcity[testIgnored"; + } else { + m_stream << "##teamcity[testFailed"; + } + m_stream << " name='" << escape( currentTestCaseInfo->name ) << '\'' + << " message='" << escape( msg.str() ) << '\'' << "]\n"; + } + m_stream.flush(); + } + + void TeamCityReporter::testCaseStarting(TestCaseInfo const& testInfo) { + m_testTimer.start(); + StreamingReporterBase::testCaseStarting(testInfo); + m_stream << "##teamcity[testStarted name='" + << escape(testInfo.name) << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::testCaseEnded(TestCaseStats const& testCaseStats) { + StreamingReporterBase::testCaseEnded(testCaseStats); + auto const& testCaseInfo = *testCaseStats.testInfo; + if (!testCaseStats.stdOut.empty()) + m_stream << "##teamcity[testStdOut name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdOut) << "']\n"; + if (!testCaseStats.stdErr.empty()) + m_stream << "##teamcity[testStdErr name='" + << escape(testCaseInfo.name) + << "' out='" << escape(testCaseStats.stdErr) << "']\n"; + m_stream << "##teamcity[testFinished name='" + << escape(testCaseInfo.name) << "' duration='" + << m_testTimer.getElapsedMilliseconds() << "']\n"; + m_stream.flush(); + } + + void TeamCityReporter::printSectionHeader(std::ostream& os) { + assert(!m_sectionStack.empty()); + + if (m_sectionStack.size() > 1) { + os << lineOfChars('-') << '\n'; + + std::vector<SectionInfo>::const_iterator + it = m_sectionStack.begin() + 1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for (; it != itEnd; ++it) + printHeaderString(os, it->name); + os << lineOfChars('-') << '\n'; + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + os << lineInfo << '\n'; + os << lineOfChars('.') << "\n\n"; + } + +} // end namespace Catch + + + + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + XmlReporter::XmlReporter( ReporterConfig&& _config ) + : StreamingReporterBase( CATCH_MOVE(_config) ), + m_xml(m_stream) + { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = true; + } + + XmlReporter::~XmlReporter() = default; + + std::string XmlReporter::getDescription() { + return "Reports test results as an XML document"; + } + + std::string XmlReporter::getStylesheetRef() const { + return std::string(); + } + + void XmlReporter::writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename"_sr, sourceInfo.file ) + .writeAttribute( "line"_sr, sourceInfo.line ); + } + + void XmlReporter::testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement("Catch2TestRun") + .writeAttribute("name"_sr, m_config->name()) + .writeAttribute("rng-seed"_sr, m_config->rngSeed()) + .writeAttribute("xml-format-version"_sr, 3) + .writeAttribute("catch2-version"_sr, libraryVersion()); + if ( m_config->testSpec().hasFilters() ) { + m_xml.writeAttribute( "filters"_sr, m_config->testSpec() ); + } + } + + void XmlReporter::testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name"_sr, trim( StringRef(testInfo.name) ) ) + .writeAttribute( "tags"_sr, testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } + + void XmlReporter::sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name"_sr, trim( StringRef(sectionInfo.name) ) ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); + } + } + + void XmlReporter::assertionStarting( AssertionInfo const& ) { } + + void XmlReporter::assertionEnded( AssertionStats const& assertionStats ) { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults || result.getResultType() == ResultWas::Warning ) { + // Print any info messages in <Info> tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info && includeResults ) { + auto t = m_xml.scopedElement( "Info" ); + writeSourceInfo( msg.lineInfo ); + t.writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + auto t = m_xml.scopedElement( "Warning" ); + writeSourceInfo( msg.lineInfo ); + t.writeText( msg.message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if ( !includeResults && result.getResultType() != ResultWas::Warning && + result.getResultType() != ResultWas::ExplicitSkip ) { + return; + } + + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success"_sr, result.succeeded() ) + .writeAttribute( "type"_sr, result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::ExplicitSkip: + m_xml.startElement( "Skip" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; + } + + if( result.hasExpression() ) + m_xml.endElement(); + } + + void XmlReporter::sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if ( --m_sectionDepth > 0 ) { + { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes"_sr, sectionStats.assertions.passed ); + e.writeAttribute( "failures"_sr, sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures"_sr, sectionStats.assertions.failedButOk ); + e.writeAttribute( "skipped"_sr, sectionStats.assertions.skipped > 0 ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds"_sr, sectionStats.durationInSeconds ); + } + // Ends assertion tag + m_xml.endElement(); + } + } + + void XmlReporter::testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success"_sr, testCaseStats.totals.assertions.allOk() ); + e.writeAttribute( "skips"_sr, testCaseStats.totals.assertions.skipped ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds"_sr, m_testCaseTimer.getElapsedSeconds() ); + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( StringRef(testCaseStats.stdOut) ), XmlFormatting::Newline ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( StringRef(testCaseStats.stdErr) ), XmlFormatting::Newline ); + + m_xml.endElement(); + } + + void XmlReporter::testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes"_sr, testRunStats.totals.assertions.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.assertions.failedButOk ) + .writeAttribute( "skips"_sr, testRunStats.totals.assertions.skipped ); + m_xml.scopedElement( "OverallResultsCases") + .writeAttribute( "successes"_sr, testRunStats.totals.testCases.passed ) + .writeAttribute( "failures"_sr, testRunStats.totals.testCases.failed ) + .writeAttribute( "expectedFailures"_sr, testRunStats.totals.testCases.failedButOk ) + .writeAttribute( "skips"_sr, testRunStats.totals.testCases.skipped ); + m_xml.endElement(); + } + + void XmlReporter::benchmarkPreparing( StringRef name ) { + m_xml.startElement("BenchmarkResults") + .writeAttribute("name"_sr, name); + } + + void XmlReporter::benchmarkStarting(BenchmarkInfo const &info) { + m_xml.writeAttribute("samples"_sr, info.samples) + .writeAttribute("resamples"_sr, info.resamples) + .writeAttribute("iterations"_sr, info.iterations) + .writeAttribute("clockResolution"_sr, info.clockResolution) + .writeAttribute("estimatedDuration"_sr, info.estimatedDuration) + .writeComment("All values in nano seconds"_sr); + } + + void XmlReporter::benchmarkEnded(BenchmarkStats<> const& benchmarkStats) { + m_xml.scopedElement("mean") + .writeAttribute("value"_sr, benchmarkStats.mean.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.mean.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.mean.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.mean.confidence_interval); + m_xml.scopedElement("standardDeviation") + .writeAttribute("value"_sr, benchmarkStats.standardDeviation.point.count()) + .writeAttribute("lowerBound"_sr, benchmarkStats.standardDeviation.lower_bound.count()) + .writeAttribute("upperBound"_sr, benchmarkStats.standardDeviation.upper_bound.count()) + .writeAttribute("ci"_sr, benchmarkStats.standardDeviation.confidence_interval); + m_xml.scopedElement("outliers") + .writeAttribute("variance"_sr, benchmarkStats.outlierVariance) + .writeAttribute("lowMild"_sr, benchmarkStats.outliers.low_mild) + .writeAttribute("lowSevere"_sr, benchmarkStats.outliers.low_severe) + .writeAttribute("highMild"_sr, benchmarkStats.outliers.high_mild) + .writeAttribute("highSevere"_sr, benchmarkStats.outliers.high_severe); + m_xml.endElement(); + } + + void XmlReporter::benchmarkFailed(StringRef error) { + m_xml.scopedElement("failed"). + writeAttribute("message"_sr, error); + m_xml.endElement(); + } + + void XmlReporter::listReporters(std::vector<ReporterDescription> const& descriptions) { + auto outerTag = m_xml.scopedElement("AvailableReporters"); + for (auto const& reporter : descriptions) { + auto inner = m_xml.scopedElement("Reporter"); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(reporter.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Description", XmlFormatting::Indent) + .writeText(reporter.description, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listListeners(std::vector<ListenerDescription> const& descriptions) { + auto outerTag = m_xml.scopedElement( "RegisteredListeners" ); + for ( auto const& listener : descriptions ) { + auto inner = m_xml.scopedElement( "Listener" ); + m_xml.startElement( "Name", XmlFormatting::Indent ) + .writeText( listener.name, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + m_xml.startElement( "Description", XmlFormatting::Indent ) + .writeText( listener.description, XmlFormatting::None ) + .endElement( XmlFormatting::Newline ); + } + } + + void XmlReporter::listTests(std::vector<TestCaseHandle> const& tests) { + auto outerTag = m_xml.scopedElement("MatchingTests"); + for (auto const& test : tests) { + auto innerTag = m_xml.scopedElement("TestCase"); + auto const& testInfo = test.getTestCaseInfo(); + m_xml.startElement("Name", XmlFormatting::Indent) + .writeText(testInfo.name, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("ClassName", XmlFormatting::Indent) + .writeText(testInfo.className, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Tags", XmlFormatting::Indent) + .writeText(testInfo.tagsAsString(), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + + auto sourceTag = m_xml.scopedElement("SourceInfo"); + m_xml.startElement("File", XmlFormatting::Indent) + .writeText(testInfo.lineInfo.file, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + m_xml.startElement("Line", XmlFormatting::Indent) + .writeText(std::to_string(testInfo.lineInfo.line), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + + void XmlReporter::listTags(std::vector<TagInfo> const& tags) { + auto outerTag = m_xml.scopedElement("TagsFromMatchingTests"); + for (auto const& tag : tags) { + auto innerTag = m_xml.scopedElement("Tag"); + m_xml.startElement("Count", XmlFormatting::Indent) + .writeText(std::to_string(tag.count), XmlFormatting::None) + .endElement(XmlFormatting::Newline); + auto aliasTag = m_xml.scopedElement("Aliases"); + for (auto const& alias : tag.spellings) { + m_xml.startElement("Alias", XmlFormatting::Indent) + .writeText(alias, XmlFormatting::None) + .endElement(XmlFormatting::Newline); + } + } + } + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif diff --git a/tests/catch2/catch_amalgamated.hpp b/tests/catch2/catch_amalgamated.hpp new file mode 100644 index 00000000..7703e957 --- /dev/null +++ b/tests/catch2/catch_amalgamated.hpp @@ -0,0 +1,14135 @@ + +// Copyright Catch2 Authors +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE.txt or copy at +// https://www.boost.org/LICENSE_1_0.txt) + +// SPDX-License-Identifier: BSL-1.0 + +// Catch v3.8.1 +// Generated: 2025-04-08 12:33:19.851017 +// ---------------------------------------------------------- +// This file is an amalgamation of multiple different files. +// You probably shouldn't edit it directly. +// ---------------------------------------------------------- +#ifndef CATCH_AMALGAMATED_HPP_INCLUDED +#define CATCH_AMALGAMATED_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2. It includes **all** of Catch2 headers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the top level folder, or to the + * corresponding internal subfolder, it should be added here. Headers + * added to the various subparts (e.g. matchers, generators, etc...), + * should go their respective catch-all headers. + */ + +#ifndef CATCH_ALL_HPP_INCLUDED +#define CATCH_ALL_HPP_INCLUDED + + + +/** \file + * This is a convenience header for Catch2's benchmarking. It includes + * **all** of Catch2 headers related to benchmarking. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `benchmark` folder, or to + * the corresponding internal (detail) subfolder, it should be added here. + */ + +#ifndef CATCH_BENCHMARK_ALL_HPP_INCLUDED +#define CATCH_BENCHMARK_ALL_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_HPP_INCLUDED +#define CATCH_BENCHMARK_HPP_INCLUDED + + + +#ifndef CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +#define CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_<feature name> form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + + + +#ifndef CATCH_PLATFORM_HPP_INCLUDED +#define CATCH_PLATFORM_HPP_INCLUDED + +// See e.g.: +// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html +#ifdef __APPLE__ +# ifndef __has_extension +# define __has_extension(x) 0 +# endif +# include <TargetConditionals.h> +# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ + (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) +# define CATCH_PLATFORM_MAC +# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS + +# if defined( WINAPI_FAMILY ) && ( WINAPI_FAMILY == WINAPI_FAMILY_APP ) +# define CATCH_PLATFORM_WINDOWS_UWP +# endif + +#elif defined(__ORBIS__) || defined(__PROSPERO__) +# define CATCH_PLATFORM_PLAYSTATION + +#endif + +#endif // CATCH_PLATFORM_HPP_INCLUDED + +#ifdef __cplusplus + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +# if (__cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) +# define CATCH_CPP20_OR_GREATER +# endif + +#endif + +// Only GCC compiler should be used in this block, so other compilers trying to +// mask themselves as GCC should be ignored. +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +// This only works on GCC 9+. so we have to also add a global suppression of Wparentheses +// for older versions of GCC. +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + _Pragma( "GCC diagnostic ignored \"-Wunused-result\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wuseless-cast\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "GCC diagnostic ignored \"-Wshadow\"" ) + +# define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P + +#endif + +#if defined(__NVCOMPILER) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "diag push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "diag pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress declared_but_not_referenced" ) +#endif + +#if defined(__CUDACC__) && !defined(__clang__) +# ifdef __NVCC_DIAG_PRAGMA_SUPPORT__ +// New pragmas introduced in CUDA 11.5+ +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "nv_diagnostic pop" ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "nv_diag_suppress 177" ) +# else +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS _Pragma( "diag_suppress 177" ) +# endif +#endif + +// clang-cl defines _MSC_VER as well as __clang__, which could cause the +// start/stop internal suppression macros to be double defined. +#if defined(__clang__) && !defined(_MSC_VER) +# define CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) +#endif // __clang__ && !_MSC_VER + +#if defined(__clang__) + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# if (__clang_major__ >= 20) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wvariadic-macro-arguments-omitted\"" ) +# elif (__clang_major__ == 19) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wc++20-extensions\"" ) +# else +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) +# endif + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wcomma\"" ) + +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wshadow\"" ) + +#endif // __clang__ + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Similarly, NVHPC's implementation of `__builtin_constant_p` has a bug which +// results in calls to the immediately evaluated lambda expressions to be +// reported as unevaluated lambdas. +// https://developer.nvidia.com/nvidia_bug/3321845. +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +#if defined( __ibmxl__ ) || defined( __CUDACC__ ) || defined( __NVCOMPILER ) +# define CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P +#endif + + + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined( CATCH_PLATFORM_WINDOWS ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( __CYGWIN__ ) || \ + defined( __QNX__ ) || \ + defined( __EMSCRIPTEN__ ) || \ + defined( __DJGPP__ ) || \ + defined( __OS400__ ) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#else +# define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Assume that some platforms do not support getenv. +#if defined( CATCH_PLATFORM_WINDOWS_UWP ) || \ + defined( CATCH_PLATFORM_PLAYSTATION ) || \ + defined( _GAMING_XBOX ) +# define CATCH_INTERNAL_CONFIG_NO_GETENV +#else +# define CATCH_INTERNAL_CONFIG_GETENV +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +// We want to defer to nvcc-specific warning suppression if we are compiled +// with nvcc masquerading for MSVC. +# if !defined( __CUDACC__ ) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + __pragma( warning( push ) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + __pragma( warning( pop ) ) +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(CATCH_PLATFORM_WINDOWS_UWP) +# define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include(<string_view>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include(<optional>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + # include <cstddef> + # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif + # endif // __has_include(<cstddef>) && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include <ciso646> + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include(<variant>) && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + + +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GETENV) && !defined(CATCH_INTERNAL_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_NO_GETENV) && !defined(CATCH_CONFIG_GETENV) +# define CATCH_CONFIG_GETENV +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined( CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED ) && \ + !defined( CATCH_CONFIG_DISABLE_EXCEPTIONS ) && \ + !defined( CATCH_CONFIG_NO_DISABLE_EXCEPTIONS ) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if defined( CATCH_INTERNAL_CONFIG_USE_BUILTIN_CONSTANT_P ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) && !defined(CATCH_CONFIG_USE_BUILTIN_CONSTANT_P) +#define CATCH_CONFIG_USE_BUILTIN_CONSTANT_P +#endif + +#if defined( CATCH_CONFIG_USE_BUILTIN_CONSTANT_P ) && \ + !defined( CATCH_CONFIG_NO_USE_BUILTIN_CONSTANT_P ) +# define CATCH_INTERNAL_IGNORE_BUT_WARN( ... ) \ + (void)__builtin_constant_p( __VA_ARGS__ ) /* NOLINT(cppcoreguidelines-pro-type-vararg, \ + hicpp-vararg) */ +#else +# define CATCH_INTERNAL_IGNORE_BUT_WARN( ... ) +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS +#endif +#if !defined( CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS ) +# define CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +#if defined( CATCH_PLATFORM_WINDOWS ) && \ + !defined( CATCH_CONFIG_COLOUR_WIN32 ) && \ + !defined( CATCH_CONFIG_NO_COLOUR_WIN32 ) && \ + !defined( CATCH_INTERNAL_CONFIG_NO_COLOUR_WIN32 ) +# define CATCH_CONFIG_COLOUR_WIN32 +#endif + +#if defined( CATCH_CONFIG_SHARED_LIBRARY ) && defined( _MSC_VER ) && \ + !defined( CATCH_CONFIG_STATIC ) +# ifdef Catch2_EXPORTS +# define CATCH_EXPORT //__declspec( dllexport ) // not needed +# else +# define CATCH_EXPORT __declspec( dllimport ) +# endif +#else +# define CATCH_EXPORT +#endif + +#endif // CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + + +#ifndef CATCH_CONTEXT_HPP_INCLUDED +#define CATCH_CONTEXT_HPP_INCLUDED + + +namespace Catch { + + class IResultCapture; + class IConfig; + + class Context { + IConfig const* m_config = nullptr; + IResultCapture* m_resultCapture = nullptr; + + CATCH_EXPORT static Context* currentContext; + friend Context& getCurrentMutableContext(); + friend Context const& getCurrentContext(); + static void createContext(); + friend void cleanUpContext(); + + public: + constexpr IResultCapture* getResultCapture() const { + return m_resultCapture; + } + constexpr IConfig const* getConfig() const { return m_config; } + constexpr void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + constexpr void setConfig( IConfig const* config ) { m_config = config; } + + }; + + Context& getCurrentMutableContext(); + + inline Context const& getCurrentContext() { + // We duplicate the logic from `getCurrentMutableContext` here, + // to avoid paying the call overhead in debug mode. + if ( !Context::currentContext ) { Context::createContext(); } + // NOLINTNEXTLINE(clang-analyzer-core.uninitialized.UndefReturn) + return *Context::currentContext; + } + + void cleanUpContext(); + + class SimplePcg32; + SimplePcg32& sharedRng(); +} + +#endif // CATCH_CONTEXT_HPP_INCLUDED + + +#ifndef CATCH_MOVE_AND_FORWARD_HPP_INCLUDED +#define CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + +#include <type_traits> + +//! Replacement for std::move with better compile time performance +#define CATCH_MOVE(...) static_cast<std::remove_reference_t<decltype(__VA_ARGS__)>&&>(__VA_ARGS__) + +//! Replacement for std::forward with better compile time performance +#define CATCH_FORWARD(...) static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__) + +#endif // CATCH_MOVE_AND_FORWARD_HPP_INCLUDED + + +#ifndef CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED +#define CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + +namespace Catch { + + //! Used to signal that an assertion macro failed + struct TestFailureException{}; + //! Used to signal that the remainder of a test should be skipped + struct TestSkipException {}; + + /** + * Outlines throwing of `TestFailureException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_failure_exception(); + + /** + * Outlines throwing of `TestSkipException` into a single TU + * + * Also handles `CATCH_CONFIG_DISABLE_EXCEPTIONS` for callers. + */ + [[noreturn]] void throw_test_skip_exception(); + +} // namespace Catch + +#endif // CATCH_TEST_FAILURE_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_NAME_HPP_INCLUDED +#define CATCH_UNIQUE_NAME_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CONFIG configuration option + * + * When generating internal unique names, there are two options. Either + * we mix in the current line number, or mix in an incrementing number. + * We prefer the latter, using `__COUNTER__`, but users might want to + * use the former. + */ + +#ifndef CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define CATCH_CONFIG_COUNTER_HPP_INCLUDED + + +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +#if defined( CATCH_INTERNAL_CONFIG_COUNTER ) && \ + !defined( CATCH_CONFIG_NO_COUNTER ) && \ + !defined( CATCH_CONFIG_COUNTER ) +# define CATCH_CONFIG_COUNTER +#endif + + +#endif // CATCH_CONFIG_COUNTER_HPP_INCLUDED +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#endif // CATCH_UNIQUE_NAME_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CAPTURE_HPP_INCLUDED +#define CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + +#include <string> + + + +#ifndef CATCH_STRINGREF_HPP_INCLUDED +#define CATCH_STRINGREF_HPP_INCLUDED + +#include <cstddef> +#include <string> +#include <iosfwd> +#include <cassert> + +#include <cstring> + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + static constexpr size_type npos{ static_cast<size_type>( -1 ) }; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef other ) const noexcept -> bool { + return m_size == other.m_size + && (std::memcmp( m_start, other.m_start, m_size ) == 0); + } + auto operator != (StringRef other) const noexcept -> bool { + return !(*this == other); + } + + constexpr auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + bool operator<(StringRef rhs) const noexcept; + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + constexpr StringRef substr(size_type start, size_type length) const noexcept { + if (start < m_size) { + const auto shortened_size = m_size - start; + return StringRef(m_start + start, (shortened_size < length) ? shortened_size : length); + } else { + return StringRef(); + } + } + + // Returns the current start pointer. May not be null-terminated. + constexpr char const* data() const noexcept { + return m_start; + } + + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + + + friend std::string& operator += (std::string& lhs, StringRef rhs); + friend std::ostream& operator << (std::ostream& os, StringRef str); + friend std::string operator+(StringRef lhs, StringRef rhs); + + /** + * Provides a three-way comparison with rhs + * + * Returns negative number if lhs < rhs, 0 if lhs == rhs, and a positive + * number if lhs > rhs + */ + int compare( StringRef rhs ) const; + }; + + + constexpr auto operator ""_sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator ""_catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +#endif // CATCH_STRINGREF_HPP_INCLUDED + + +#ifndef CATCH_RESULT_TYPE_HPP_INCLUDED +#define CATCH_RESULT_TYPE_HPP_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + // TODO: Should explicit skip be considered "not OK" (cf. isOk)? I.e., should it have the failure bit? + ExplicitSkip = 4, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + constexpr bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + constexpr bool isJustInfo( int flags ) { return flags == ResultWas::Info; } + + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + constexpr ResultDisposition::Flags operator|( ResultDisposition::Flags lhs, + ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | + static_cast<int>( rhs ) ); + } + + constexpr bool isFalseTest( int flags ) { + return ( flags & ResultDisposition::FalseTest ) != 0; + } + constexpr bool shouldSuppressFailure( int flags ) { + return ( flags & ResultDisposition::SuppressFail ) != 0; + } + +} // end namespace Catch + +#endif // CATCH_RESULT_TYPE_HPP_INCLUDED + + +#ifndef CATCH_UNIQUE_PTR_HPP_INCLUDED +#define CATCH_UNIQUE_PTR_HPP_INCLUDED + +#include <cassert> +#include <type_traits> + + +namespace Catch { +namespace Detail { + /** + * A reimplementation of `std::unique_ptr` for improved compilation performance + * + * Does not support arrays nor custom deleters. + */ + template <typename T> + class unique_ptr { + T* m_ptr; + public: + constexpr unique_ptr(std::nullptr_t = nullptr): + m_ptr{} + {} + explicit constexpr unique_ptr(T* ptr): + m_ptr(ptr) + {} + + template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>> + unique_ptr(unique_ptr<U>&& from): + m_ptr(from.release()) + {} + + template <typename U, typename = std::enable_if_t<std::is_base_of<T, U>::value>> + unique_ptr& operator=(unique_ptr<U>&& from) { + reset(from.release()); + + return *this; + } + + unique_ptr(unique_ptr const&) = delete; + unique_ptr& operator=(unique_ptr const&) = delete; + + unique_ptr(unique_ptr&& rhs) noexcept: + m_ptr(rhs.m_ptr) { + rhs.m_ptr = nullptr; + } + unique_ptr& operator=(unique_ptr&& rhs) noexcept { + reset(rhs.release()); + + return *this; + } + + ~unique_ptr() { + delete m_ptr; + } + + T& operator*() { + assert(m_ptr); + return *m_ptr; + } + T const& operator*() const { + assert(m_ptr); + return *m_ptr; + } + T* operator->() noexcept { + assert(m_ptr); + return m_ptr; + } + T const* operator->() const noexcept { + assert(m_ptr); + return m_ptr; + } + + T* get() { return m_ptr; } + T const* get() const { return m_ptr; } + + void reset(T* ptr = nullptr) { + delete m_ptr; + m_ptr = ptr; + } + + T* release() { + auto temp = m_ptr; + m_ptr = nullptr; + return temp; + } + + explicit operator bool() const { + return m_ptr; + } + + friend void swap(unique_ptr& lhs, unique_ptr& rhs) { + auto temp = lhs.m_ptr; + lhs.m_ptr = rhs.m_ptr; + rhs.m_ptr = temp; + } + }; + + //! Specialization to cause compile-time error for arrays + template <typename T> + class unique_ptr<T[]>; + + template <typename T, typename... Args> + unique_ptr<T> make_unique(Args&&... args) { + return unique_ptr<T>(new T(CATCH_FORWARD(args)...)); + } + + +} // end namespace Detail +} // end namespace Catch + +#endif // CATCH_UNIQUE_PTR_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CLOCK_HPP_INCLUDED +#define CATCH_CLOCK_HPP_INCLUDED + +#include <chrono> + +namespace Catch { + namespace Benchmark { + using IDuration = std::chrono::nanoseconds; + using FDuration = std::chrono::duration<double, std::nano>; + + template <typename Clock> + using TimePoint = typename Clock::time_point; + + using default_clock = std::chrono::steady_clock; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CLOCK_HPP_INCLUDED + +namespace Catch { + + // We cannot forward declare the type with default template argument + // multiple times, so it is split out into a separate header so that + // we can prevent multiple declarations in dependees + template <typename Duration = Benchmark::FDuration> + struct BenchmarkStats; + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_FWD_HPP_INCLUDED + +namespace Catch { + + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct SectionEndInfo; + struct MessageInfo; + struct MessageBuilder; + struct Counts; + struct AssertionReaction; + struct SourceLineInfo; + + class ITransientExpression; + class IGeneratorTracker; + + struct BenchmarkInfo; + + namespace Generators { + class GeneratorUntypedBase; + using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>; + } + + + class IResultCapture { + public: + virtual ~IResultCapture(); + + virtual void notifyAssertionStarted( AssertionInfo const& info ) = 0; + virtual bool sectionStarted( StringRef sectionName, + SourceLineInfo const& sectionLineInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionEndInfo&& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo&& endInfo ) = 0; + + virtual IGeneratorTracker* + acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ) = 0; + virtual IGeneratorTracker* + createGeneratorTracker( StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr&& generator ) = 0; + + virtual void benchmarkPreparing( StringRef name ) = 0; + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats<> const& stats ) = 0; + virtual void benchmarkFailed( StringRef error ) = 0; + + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual void emplaceUnscopedMessage( MessageBuilder&& builder ) = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) = 0; + virtual void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + std::string&& message, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) = 0; + virtual void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string&& message, + AssertionReaction& reaction ) = 0; + virtual void handleIncomplete + ( AssertionInfo const& info ) = 0; + virtual void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) = 0; + + + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + + // Deprecated, do not use: + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + virtual void exceptionEarlyReported() = 0; + }; + + IResultCapture& getResultCapture(); +} + +#endif // CATCH_INTERFACES_CAPTURE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_CONFIG_HPP_INCLUDED +#define CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_NONCOPYABLE_HPP_INCLUDED +#define CATCH_NONCOPYABLE_HPP_INCLUDED + +namespace Catch { + namespace Detail { + + //! Deriving classes become noncopyable and nonmovable + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable&& ) = delete; + NonCopyable& operator=( NonCopyable const& ) = delete; + NonCopyable& operator=( NonCopyable&& ) = delete; + + protected: + NonCopyable() noexcept = default; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_NONCOPYABLE_HPP_INCLUDED + +#include <chrono> +#include <iosfwd> +#include <string> +#include <vector> + +namespace Catch { + + enum class Verbosity { + Quiet = 0, + Normal, + High + }; + + struct WarnAbout { enum What { + Nothing = 0x00, + //! A test case or leaf section did not run any assertions + NoAssertions = 0x01, + //! A command line test spec matched no test cases + UnmatchedTestSpec = 0x02, + }; }; + + enum class ShowDurations { + DefaultForReporter, + Always, + Never + }; + enum class TestRunOrder { + Declared, + LexicographicallySorted, + Randomized + }; + enum class ColourMode : std::uint8_t { + //! Let Catch2 pick implementation based on platform detection + PlatformDefault, + //! Use ANSI colour code escapes + ANSI, + //! Use Win32 console colour API + Win32, + //! Don't use any colour + None + }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; + + class TestSpec; + class IStream; + + class IConfig : public Detail::NonCopyable { + public: + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual StringRef name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual bool warnAboutUnmatchedTestSpecs() const = 0; + virtual bool zeroTestsCountAsSuccess() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations showDurations() const = 0; + virtual double minDuration() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual bool hasTestFilters() const = 0; + virtual std::vector<std::string> const& getTestsOrTags() const = 0; + virtual TestRunOrder runOrder() const = 0; + virtual uint32_t rngSeed() const = 0; + virtual unsigned int shardCount() const = 0; + virtual unsigned int shardIndex() const = 0; + virtual ColourMode defaultColourMode() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; + + virtual bool skipBenchmarks() const = 0; + virtual bool benchmarkNoAnalysis() const = 0; + virtual unsigned int benchmarkSamples() const = 0; + virtual double benchmarkConfidenceInterval() const = 0; + virtual unsigned int benchmarkResamples() const = 0; + virtual std::chrono::milliseconds benchmarkWarmupTime() const = 0; + }; +} + +#endif // CATCH_INTERFACES_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED +#define CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + class TestCaseHandle; + struct TestCaseInfo; + class ITestCaseRegistry; + class IExceptionTranslatorRegistry; + class IExceptionTranslator; + class ReporterRegistry; + class IReporterFactory; + class ITagAliasRegistry; + class ITestInvoker; + class IMutableEnumValuesRegistry; + struct SourceLineInfo; + + class StartupExceptionRegistry; + class EventListenerFactory; + + using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; + + class IRegistryHub { + public: + virtual ~IRegistryHub(); // = default + + virtual ReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry const& getExceptionTranslatorRegistry() const = 0; + + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; + }; + + class IMutableRegistryHub { + public: + virtual ~IMutableRegistryHub(); // = default + virtual void registerReporter( std::string const& name, IReporterFactoryPtr factory ) = 0; + virtual void registerListener( Detail::unique_ptr<EventListenerFactory> factory ) = 0; + virtual void registerTest(Detail::unique_ptr<TestCaseInfo>&& testInfo, Detail::unique_ptr<ITestInvoker>&& invoker) = 0; + virtual void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; + virtual IMutableEnumValuesRegistry& getMutableEnumValuesRegistry() = 0; + }; + + IRegistryHub const& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + +#endif // CATCH_INTERFACES_REGISTRY_HUB_HPP_INCLUDED + + +#ifndef CATCH_BENCHMARK_STATS_HPP_INCLUDED +#define CATCH_BENCHMARK_STATS_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_HPP_INCLUDED +#define CATCH_ESTIMATE_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + template <typename Type> + struct Estimate { + Type point; + Type lower_bound; + Type upper_bound; + double confidence_interval; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED +#define CATCH_OUTLIER_CLASSIFICATION_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + struct OutlierClassification { + int samples_seen = 0; + int low_severe = 0; // more than 3 times IQR below Q1 + int low_mild = 0; // 1.5 to 3 times IQR below Q1 + int high_mild = 0; // 1.5 to 3 times IQR above Q3 + int high_severe = 0; // more than 3 times IQR above Q3 + + constexpr int total() const { + return low_severe + low_mild + high_mild + high_severe; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OUTLIERS_CLASSIFICATION_HPP_INCLUDED +// The fwd decl & default specialization needs to be seen by VS2017 before +// BenchmarkStats itself, or VS2017 will report compilation error. + +#include <string> +#include <vector> + +namespace Catch { + + struct BenchmarkInfo { + std::string name; + double estimatedDuration; + int iterations; + unsigned int samples; + unsigned int resamples; + double clockResolution; + double clockCost; + }; + + // We need to keep template parameter for backwards compatibility, + // but we also do not want to use the template paraneter. + template <class Dummy> + struct BenchmarkStats { + BenchmarkInfo info; + + std::vector<Benchmark::FDuration> samples; + Benchmark::Estimate<Benchmark::FDuration> mean; + Benchmark::Estimate<Benchmark::FDuration> standardDeviation; + Benchmark::OutlierClassification outliers; + double outlierVariance; + }; + + +} // end namespace Catch + +#endif // CATCH_BENCHMARK_STATS_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ENVIRONMENT_HPP_INCLUDED +#define CATCH_ENVIRONMENT_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + struct EnvironmentEstimate { + FDuration mean; + OutlierClassification outliers; + }; + struct Environment { + EnvironmentEstimate clock_resolution; + EnvironmentEstimate clock_cost; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ENVIRONMENT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_EXECUTION_PLAN_HPP_INCLUDED +#define CATCH_EXECUTION_PLAN_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED +#define CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_CHRONOMETER_HPP_INCLUDED +#define CATCH_CHRONOMETER_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_OPTIMIZER_HPP_INCLUDED +#define CATCH_OPTIMIZER_HPP_INCLUDED + +#if defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) +# include <atomic> // atomic_thread_fence +#endif + + +#include <type_traits> + +namespace Catch { + namespace Benchmark { +#if defined(__GNUC__) || defined(__clang__) + template <typename T> + inline void keep_memory(T* p) { + asm volatile("" : : "g"(p) : "memory"); + } + inline void keep_memory() { + asm volatile("" : : : "memory"); + } + + namespace Detail { + inline void optimizer_barrier() { keep_memory(); } + } // namespace Detail +#elif defined(_MSC_VER) || defined(__IAR_SYSTEMS_ICC__) + +#if defined(_MSVC_VER) +#pragma optimize("", off) +#elif defined(__IAR_SYSTEMS_ICC__) +// For IAR the pragma only affects the following function +#pragma optimize=disable +#endif + template <typename T> + inline void keep_memory(T* p) { + // thanks @milleniumbug + *reinterpret_cast<char volatile*>(p) = *reinterpret_cast<char const volatile*>(p); + } + // TODO equivalent keep_memory() +#if defined(_MSVC_VER) +#pragma optimize("", on) +#endif + + namespace Detail { + inline void optimizer_barrier() { + std::atomic_thread_fence(std::memory_order_seq_cst); + } + } // namespace Detail + +#endif + + template <typename T> + inline void deoptimize_value(T&& x) { + keep_memory(&x); + } + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<!std::is_same<void, decltype(fn(args...))>::value> { + deoptimize_value(CATCH_FORWARD(fn) (CATCH_FORWARD(args)...)); + } + + template <typename Fn, typename... Args> + inline auto invoke_deoptimized(Fn&& fn, Args&&... args) -> std::enable_if_t<std::is_same<void, decltype(fn(args...))>::value> { + CATCH_FORWARD((fn)) (CATCH_FORWARD(args)...); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_OPTIMIZER_HPP_INCLUDED + + +#ifndef CATCH_META_HPP_INCLUDED +#define CATCH_META_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { + template <typename> + struct true_given : std::true_type {}; + + struct is_callable_tester { + template <typename Fun, typename... Args> + static true_given<decltype(std::declval<Fun>()(std::declval<Args>()...))> test(int); + template <typename...> + static std::false_type test(...); + }; + + template <typename T> + struct is_callable; + + template <typename Fun, typename... Args> + struct is_callable<Fun(Args...)> : decltype(is_callable_tester::test<Fun, Args...>(0)) {}; + + +#if defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703 + // std::result_of is deprecated in C++17 and removed in C++20. Hence, it is + // replaced with std::invoke_result here. + template <typename Func, typename... U> + using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::invoke_result_t<Func, U...>>>; +#else + template <typename Func, typename... U> + using FunctionReturnType = std::remove_reference_t<std::remove_cv_t<std::result_of_t<Func(U...)>>>; +#endif + +} // namespace Catch + +namespace mpl_{ + struct na; +} + +#endif // CATCH_META_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + struct ChronometerConcept { + virtual void start() = 0; + virtual void finish() = 0; + virtual ~ChronometerConcept(); // = default; + + ChronometerConcept() = default; + ChronometerConcept(ChronometerConcept const&) = default; + ChronometerConcept& operator=(ChronometerConcept const&) = default; + }; + template <typename Clock> + struct ChronometerModel final : public ChronometerConcept { + void start() override { started = Clock::now(); } + void finish() override { finished = Clock::now(); } + + IDuration elapsed() const { + return std::chrono::duration_cast<std::chrono::nanoseconds>( + finished - started ); + } + + TimePoint<Clock> started; + TimePoint<Clock> finished; + }; + } // namespace Detail + + struct Chronometer { + public: + template <typename Fun> + void measure(Fun&& fun) { measure(CATCH_FORWARD(fun), is_callable<Fun(int)>()); } + + int runs() const { return repeats; } + + Chronometer(Detail::ChronometerConcept& meter, int repeats_) + : impl(&meter) + , repeats(repeats_) {} + + private: + template <typename Fun> + void measure(Fun&& fun, std::false_type) { + measure([&fun](int) { return fun(); }, std::true_type()); + } + + template <typename Fun> + void measure(Fun&& fun, std::true_type) { + Detail::optimizer_barrier(); + impl->start(); + for (int i = 0; i < repeats; ++i) invoke_deoptimized(fun, i); + impl->finish(); + Detail::optimizer_barrier(); + } + + Detail::ChronometerConcept* impl; + int repeats; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CHRONOMETER_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T, typename U> + struct is_related + : std::is_same<std::decay_t<T>, std::decay_t<U>> {}; + + /// We need to reinvent std::function because every piece of code that might add overhead + /// in a measurement context needs to have consistent performance characteristics so that we + /// can account for it in the measurement. + /// Implementations of std::function with optimizations that aren't always applicable, like + /// small buffer optimizations, are not uncommon. + /// This is effectively an implementation of std::function without any such optimizations; + /// it may be slow, but it is consistently slow. + struct BenchmarkFunction { + private: + struct callable { + virtual void call(Chronometer meter) const = 0; + virtual ~callable(); // = default; + + callable() = default; + callable(callable&&) = default; + callable& operator=(callable&&) = default; + }; + template <typename Fun> + struct model : public callable { + model(Fun&& fun_) : fun(CATCH_MOVE(fun_)) {} + model(Fun const& fun_) : fun(fun_) {} + + void call(Chronometer meter) const override { + call(meter, is_callable<Fun(Chronometer)>()); + } + void call(Chronometer meter, std::true_type) const { + fun(meter); + } + void call(Chronometer meter, std::false_type) const { + meter.measure(fun); + } + + Fun fun; + }; + + public: + BenchmarkFunction(); + + template <typename Fun, + std::enable_if_t<!is_related<Fun, BenchmarkFunction>::value, int> = 0> + BenchmarkFunction(Fun&& fun) + : f(new model<std::decay_t<Fun>>(CATCH_FORWARD(fun))) {} + + BenchmarkFunction( BenchmarkFunction&& that ) noexcept: + f( CATCH_MOVE( that.f ) ) {} + + BenchmarkFunction& + operator=( BenchmarkFunction&& that ) noexcept { + f = CATCH_MOVE( that.f ); + return *this; + } + + void operator()(Chronometer meter) const { f->call(meter); } + + private: + Catch::Detail::unique_ptr<callable> f; + }; + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_BENCHMARK_FUNCTION_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_REPEAT_HPP_INCLUDED +#define CATCH_REPEAT_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Fun> + struct repeater { + void operator()(int k) const { + for (int i = 0; i < k; ++i) { + fun(); + } + } + Fun fun; + }; + template <typename Fun> + repeater<std::decay_t<Fun>> repeat(Fun&& fun) { + return { CATCH_FORWARD(fun) }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_REPEAT_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED +#define CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_MEASURE_HPP_INCLUDED +#define CATCH_MEASURE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_COMPLETE_INVOKE_HPP_INCLUDED +#define CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T> + struct CompleteType { using type = T; }; + template <> + struct CompleteType<void> { struct type {}; }; + + template <typename T> + using CompleteType_t = typename CompleteType<T>::type; + + template <typename Result> + struct CompleteInvoker { + template <typename Fun, typename... Args> + static Result invoke(Fun&& fun, Args&&... args) { + return CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + } + }; + template <> + struct CompleteInvoker<void> { + template <typename Fun, typename... Args> + static CompleteType_t<void> invoke(Fun&& fun, Args&&... args) { + CATCH_FORWARD(fun)(CATCH_FORWARD(args)...); + return {}; + } + }; + + // invoke and not return void :( + template <typename Fun, typename... Args> + CompleteType_t<FunctionReturnType<Fun, Args...>> complete_invoke(Fun&& fun, Args&&... args) { + return CompleteInvoker<FunctionReturnType<Fun, Args...>>::invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + } + + } // namespace Detail + + template <typename Fun> + Detail::CompleteType_t<FunctionReturnType<Fun>> user_code(Fun&& fun) { + return Detail::complete_invoke(CATCH_FORWARD(fun)); + } + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_COMPLETE_INVOKE_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_TIMING_HPP_INCLUDED +#define CATCH_TIMING_HPP_INCLUDED + + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + template <typename Result> + struct Timing { + IDuration elapsed; + Result result; + int iterations; + }; + template <typename Func, typename... Args> + using TimingOf = Timing<Detail::CompleteType_t<FunctionReturnType<Func, Args...>>>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_TIMING_HPP_INCLUDED + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun, typename... Args> + TimingOf<Fun, Args...> measure(Fun&& fun, Args&&... args) { + auto start = Clock::now(); + auto&& r = Detail::complete_invoke(CATCH_FORWARD(fun), CATCH_FORWARD(args)...); + auto end = Clock::now(); + auto delta = end - start; + return { delta, CATCH_FORWARD(r), 1 }; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_MEASURE_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock, typename Fun> + TimingOf<Fun, int> measure_one(Fun&& fun, int iters, std::false_type) { + return Detail::measure<Clock>(fun, iters); + } + template <typename Clock, typename Fun> + TimingOf<Fun, Chronometer> measure_one(Fun&& fun, int iters, std::true_type) { + Detail::ChronometerModel<Clock> meter; + auto&& result = Detail::complete_invoke(fun, Chronometer(meter, iters)); + + return { meter.elapsed(), CATCH_MOVE(result), iters }; + } + + template <typename Clock, typename Fun> + using run_for_at_least_argument_t = std::conditional_t<is_callable<Fun(Chronometer)>::value, Chronometer, int>; + + + [[noreturn]] + void throw_optimized_away_error(); + + template <typename Clock, typename Fun> + TimingOf<Fun, run_for_at_least_argument_t<Clock, Fun>> + run_for_at_least(IDuration how_long, + const int initial_iterations, + Fun&& fun) { + auto iters = initial_iterations; + while (iters < (1 << 30)) { + auto&& Timing = measure_one<Clock>(fun, iters, is_callable<Fun(Chronometer)>()); + + if (Timing.elapsed >= how_long) { + return { Timing.elapsed, CATCH_MOVE(Timing.result), iters }; + } + iters *= 2; + } + throw_optimized_away_error(); + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_RUN_FOR_AT_LEAST_HPP_INCLUDED + +#include <vector> + +namespace Catch { + namespace Benchmark { + struct ExecutionPlan { + int iterations_per_sample; + FDuration estimated_duration; + Detail::BenchmarkFunction benchmark; + FDuration warmup_time; + int warmup_iterations; + + template <typename Clock> + std::vector<FDuration> run(const IConfig &cfg, Environment env) const { + // warmup a bit + Detail::run_for_at_least<Clock>( + std::chrono::duration_cast<IDuration>( warmup_time ), + warmup_iterations, + Detail::repeat( []() { return Clock::now(); } ) + ); + + std::vector<FDuration> times; + const auto num_samples = cfg.benchmarkSamples(); + times.reserve( num_samples ); + for ( size_t i = 0; i < num_samples; ++i ) { + Detail::ChronometerModel<Clock> model; + this->benchmark( Chronometer( model, iterations_per_sample ) ); + auto sample_time = model.elapsed() - env.clock_cost.mean; + if ( sample_time < FDuration::zero() ) { + sample_time = FDuration::zero(); + } + times.push_back(sample_time / iterations_per_sample); + } + return times; + } + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_EXECUTION_PLAN_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ESTIMATE_CLOCK_HPP_INCLUDED +#define CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_STATS_HPP_INCLUDED +#define CATCH_STATS_HPP_INCLUDED + + +#include <vector> + +namespace Catch { + namespace Benchmark { + namespace Detail { + using sample = std::vector<double>; + + double weighted_average_quantile( int k, + int q, + double* first, + double* last ); + + OutlierClassification + classify_outliers( double const* first, double const* last ); + + double mean( double const* first, double const* last ); + + double normal_cdf( double x ); + + double erfc_inv(double x); + + double normal_quantile(double p); + + Estimate<double> + bootstrap( double confidence_level, + double* first, + double* last, + sample const& resample, + double ( *estimator )( double const*, double const* ) ); + + struct bootstrap_analysis { + Estimate<double> mean; + Estimate<double> standard_deviation; + double outlier_variance; + }; + + bootstrap_analysis analyse_samples(double confidence_level, + unsigned int n_resamples, + double* first, + double* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_STATS_HPP_INCLUDED + +#include <algorithm> +#include <vector> +#include <cmath> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename Clock> + std::vector<double> resolution(int k) { + const size_t points = static_cast<size_t>( k + 1 ); + // To avoid overhead from the branch inside vector::push_back, + // we allocate them all and then overwrite. + std::vector<TimePoint<Clock>> times(points); + for ( auto& time : times ) { + time = Clock::now(); + } + + std::vector<double> deltas; + deltas.reserve(static_cast<size_t>(k)); + for ( size_t idx = 1; idx < points; ++idx ) { + deltas.push_back( static_cast<double>( + ( times[idx] - times[idx - 1] ).count() ) ); + } + + return deltas; + } + + constexpr auto warmup_iterations = 10000; + constexpr auto warmup_time = std::chrono::milliseconds(100); + constexpr auto minimum_ticks = 1000; + constexpr auto warmup_seed = 10000; + constexpr auto clock_resolution_estimation_time = std::chrono::milliseconds(500); + constexpr auto clock_cost_estimation_time_limit = std::chrono::seconds(1); + constexpr auto clock_cost_estimation_tick_limit = 100000; + constexpr auto clock_cost_estimation_time = std::chrono::milliseconds(10); + constexpr auto clock_cost_estimation_iterations = 10000; + + template <typename Clock> + int warmup() { + return run_for_at_least<Clock>(warmup_time, warmup_seed, &resolution<Clock>) + .iterations; + } + template <typename Clock> + EnvironmentEstimate estimate_clock_resolution(int iterations) { + auto r = run_for_at_least<Clock>(clock_resolution_estimation_time, iterations, &resolution<Clock>) + .result; + return { + FDuration(mean(r.data(), r.data() + r.size())), + classify_outliers(r.data(), r.data() + r.size()), + }; + } + template <typename Clock> + EnvironmentEstimate estimate_clock_cost(FDuration resolution) { + auto time_limit = (std::min)( + resolution * clock_cost_estimation_tick_limit, + FDuration(clock_cost_estimation_time_limit)); + auto time_clock = [](int k) { + return Detail::measure<Clock>([k] { + for (int i = 0; i < k; ++i) { + volatile auto ignored = Clock::now(); + (void)ignored; + } + }).elapsed; + }; + time_clock(1); + int iters = clock_cost_estimation_iterations; + auto&& r = run_for_at_least<Clock>(clock_cost_estimation_time, iters, time_clock); + std::vector<double> times; + int nsamples = static_cast<int>(std::ceil(time_limit / r.elapsed)); + times.reserve(static_cast<size_t>(nsamples)); + for ( int s = 0; s < nsamples; ++s ) { + times.push_back( static_cast<double>( + ( time_clock( r.iterations ) / r.iterations ) + .count() ) ); + } + return { + FDuration(mean(times.data(), times.data() + times.size())), + classify_outliers(times.data(), times.data() + times.size()), + }; + } + + template <typename Clock> + Environment measure_environment() { +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + static Catch::Detail::unique_ptr<Environment> env; +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + if (env) { + return *env; + } + + auto iters = Detail::warmup<Clock>(); + auto resolution = Detail::estimate_clock_resolution<Clock>(iters); + auto cost = Detail::estimate_clock_cost<Clock>(resolution.mean); + + env = Catch::Detail::make_unique<Environment>( Environment{resolution, cost} ); + return *env; + } + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ESTIMATE_CLOCK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_ANALYSE_HPP_INCLUDED +#define CATCH_ANALYSE_HPP_INCLUDED + + + +// Adapted from donated nonius code. + +#ifndef CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED +#define CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +#include <vector> + +namespace Catch { + namespace Benchmark { + struct SampleAnalysis { + std::vector<FDuration> samples; + Estimate<FDuration> mean; + Estimate<FDuration> standard_deviation; + OutlierClassification outliers; + double outlier_variance; + }; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_SAMPLE_ANALYSIS_HPP_INCLUDED + + +namespace Catch { + class IConfig; + + namespace Benchmark { + namespace Detail { + SampleAnalysis analyse(const IConfig &cfg, FDuration* first, FDuration* last); + } // namespace Detail + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_ANALYSE_HPP_INCLUDED + +#include <algorithm> +#include <chrono> +#include <exception> +#include <string> +#include <cmath> + +namespace Catch { + namespace Benchmark { + struct Benchmark { + Benchmark(std::string&& benchmarkName) + : name(CATCH_MOVE(benchmarkName)) {} + + template <class FUN> + Benchmark(std::string&& benchmarkName , FUN &&func) + : fun(CATCH_MOVE(func)), name(CATCH_MOVE(benchmarkName)) {} + + template <typename Clock> + ExecutionPlan prepare(const IConfig &cfg, Environment env) { + auto min_time = env.clock_resolution.mean * Detail::minimum_ticks; + auto run_time = std::max(min_time, std::chrono::duration_cast<decltype(min_time)>(cfg.benchmarkWarmupTime())); + auto&& test = Detail::run_for_at_least<Clock>(std::chrono::duration_cast<IDuration>(run_time), 1, fun); + int new_iters = static_cast<int>(std::ceil(min_time * test.iterations / test.elapsed)); + return { new_iters, test.elapsed / test.iterations * new_iters * cfg.benchmarkSamples(), CATCH_MOVE(fun), std::chrono::duration_cast<FDuration>(cfg.benchmarkWarmupTime()), Detail::warmup_iterations }; + } + + template <typename Clock = default_clock> + void run() { + static_assert( Clock::is_steady, + "Benchmarking clock should be steady" ); + auto const* cfg = getCurrentContext().getConfig(); + + auto env = Detail::measure_environment<Clock>(); + + getResultCapture().benchmarkPreparing(name); + CATCH_TRY{ + auto plan = user_code([&] { + return prepare<Clock>(*cfg, env); + }); + + BenchmarkInfo info { + CATCH_MOVE(name), + plan.estimated_duration.count(), + plan.iterations_per_sample, + cfg->benchmarkSamples(), + cfg->benchmarkResamples(), + env.clock_resolution.mean.count(), + env.clock_cost.mean.count() + }; + + getResultCapture().benchmarkStarting(info); + + auto samples = user_code([&] { + return plan.template run<Clock>(*cfg, env); + }); + + auto analysis = Detail::analyse(*cfg, samples.data(), samples.data() + samples.size()); + BenchmarkStats<> stats{ CATCH_MOVE(info), CATCH_MOVE(analysis.samples), analysis.mean, analysis.standard_deviation, analysis.outliers, analysis.outlier_variance }; + getResultCapture().benchmarkEnded(stats); + } CATCH_CATCH_ANON (TestFailureException const&) { + getResultCapture().benchmarkFailed("Benchmark failed due to failed assertion"_sr); + } CATCH_CATCH_ALL{ + getResultCapture().benchmarkFailed(translateActiveException()); + // We let the exception go further up so that the + // test case is marked as failed. + std::rethrow_exception(std::current_exception()); + } + } + + // sets lambda to be used in fun *and* executes benchmark! + template <typename Fun, std::enable_if_t<!Detail::is_related<Fun, Benchmark>::value, int> = 0> + Benchmark & operator=(Fun func) { + auto const* cfg = getCurrentContext().getConfig(); + if (!cfg->skipBenchmarks()) { + fun = Detail::BenchmarkFunction(func); + run(); + } + return *this; + } + + explicit operator bool() { + return true; + } + + private: + Detail::BenchmarkFunction fun; + std::string name; + }; + } +} // namespace Catch + +#define INTERNAL_CATCH_GET_1_ARG(arg1, arg2, ...) arg1 +#define INTERNAL_CATCH_GET_2_ARG(arg1, arg2, ...) arg2 + +#define INTERNAL_CATCH_BENCHMARK(BenchmarkName, name, benchmarkIndex)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&](int benchmarkIndex) + +#define INTERNAL_CATCH_BENCHMARK_ADVANCED(BenchmarkName, name)\ + if( Catch::Benchmark::Benchmark BenchmarkName{name} ) \ + BenchmarkName = [&] + +#if defined(CATCH_CONFIG_PREFIX_ALL) + +#define CATCH_BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define CATCH_BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#else + +#define BENCHMARK(...) \ + INTERNAL_CATCH_BENCHMARK(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), INTERNAL_CATCH_GET_1_ARG(__VA_ARGS__,,), INTERNAL_CATCH_GET_2_ARG(__VA_ARGS__,,)) +#define BENCHMARK_ADVANCED(name) \ + INTERNAL_CATCH_BENCHMARK_ADVANCED(INTERNAL_CATCH_UNIQUE_NAME(CATCH2_INTERNAL_BENCHMARK_), name) + +#endif + +#endif // CATCH_BENCHMARK_HPP_INCLUDED + + +// Adapted from donated nonius code. + +#ifndef CATCH_CONSTRUCTOR_HPP_INCLUDED +#define CATCH_CONSTRUCTOR_HPP_INCLUDED + + +#include <type_traits> + +namespace Catch { + namespace Benchmark { + namespace Detail { + template <typename T, bool Destruct> + struct ObjectStorage + { + ObjectStorage() = default; + + ObjectStorage(const ObjectStorage& other) + { + new(&data) T(other.stored_object()); + } + + ObjectStorage(ObjectStorage&& other) + { + new(data) T(CATCH_MOVE(other.stored_object())); + } + + ~ObjectStorage() { destruct_on_exit<T>(); } + + template <typename... Args> + void construct(Args&&... args) + { + new (data) T(CATCH_FORWARD(args)...); + } + + template <bool AllowManualDestruction = !Destruct> + std::enable_if_t<AllowManualDestruction> destruct() + { + stored_object().~T(); + } + + private: + // If this is a constructor benchmark, destruct the underlying object + template <typename U> + void destruct_on_exit(std::enable_if_t<Destruct, U>* = nullptr) { destruct<true>(); } + // Otherwise, don't + template <typename U> + void destruct_on_exit(std::enable_if_t<!Destruct, U>* = nullptr) { } + +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + T& stored_object() { return *reinterpret_cast<T*>( data ); } + + T const& stored_object() const { + return *reinterpret_cast<T const*>( data ); + } +#if defined( __GNUC__ ) && __GNUC__ <= 6 +# pragma GCC diagnostic pop +#endif + + alignas( T ) unsigned char data[sizeof( T )]{}; + }; + } // namespace Detail + + template <typename T> + using storage_for = Detail::ObjectStorage<T, true>; + + template <typename T> + using destructable_object = Detail::ObjectStorage<T, false>; + } // namespace Benchmark +} // namespace Catch + +#endif // CATCH_CONSTRUCTOR_HPP_INCLUDED + +#endif // CATCH_BENCHMARK_ALL_HPP_INCLUDED + + +#ifndef CATCH_APPROX_HPP_INCLUDED +#define CATCH_APPROX_HPP_INCLUDED + + + +#ifndef CATCH_TOSTRING_HPP_INCLUDED +#define CATCH_TOSTRING_HPP_INCLUDED + + +#include <vector> +#include <cstddef> +#include <type_traits> +#include <string> + + + + +/** \file + * Wrapper for the WCHAR configuration option + * + * We want to support platforms that do not provide `wchar_t`, so we + * sometimes have to disable providing wchar_t overloads through Catch2, + * e.g. the StringMaker specialization for `std::wstring`. + */ + +#ifndef CATCH_CONFIG_WCHAR_HPP_INCLUDED +#define CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +// We assume that WCHAR should be enabled by default, and only disabled +// for a shortlist (so far only DJGPP) of compilers. + +#if defined(__DJGPP__) +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +#if !defined( CATCH_INTERNAL_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_NO_WCHAR ) && \ + !defined( CATCH_CONFIG_WCHAR ) +# define CATCH_CONFIG_WCHAR +#endif + +#endif // CATCH_CONFIG_WCHAR_HPP_INCLUDED + + +#ifndef CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED +#define CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#include <iosfwd> +#include <cstddef> +#include <ostream> +#include <string> + +namespace Catch { + + class ReusableStringStream : Detail::NonCopyable { + std::size_t m_index; + std::ostream* m_oss; + public: + ReusableStringStream(); + ~ReusableStringStream(); + + //! Returns the serialized state + std::string str() const; + //! Sets internal state to `str` + void str(std::string const& str); + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +// Old versions of GCC do not understand -Wnonnull-compare +#pragma GCC diagnostic ignored "-Wpragmas" +// Streaming a function pointer triggers Waddress and Wnonnull-compare +// on GCC, because it implicitly converts it to bool and then decides +// that the check it uses (a? true : false) is tautological and cannot +// be null... +#pragma GCC diagnostic ignored "-Waddress" +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + + template<typename T> + auto operator << ( T const& value ) -> ReusableStringStream& { + *m_oss << value; + return *this; + } + +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + auto get() -> std::ostream& { return *m_oss; } + }; +} + +#endif // CATCH_REUSABLE_STRING_STREAM_HPP_INCLUDED + + +#ifndef CATCH_VOID_TYPE_HPP_INCLUDED +#define CATCH_VOID_TYPE_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + + template <typename...> + struct make_void { using type = void; }; + + template <typename... Ts> + using void_t = typename make_void<Ts...>::type; + + } // namespace Detail +} // namespace Catch + + +#endif // CATCH_VOID_TYPE_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#include <vector> + +namespace Catch { + + namespace Detail { + struct EnumInfo { + StringRef m_name; + std::vector<std::pair<int, StringRef>> m_values; + + ~EnumInfo(); + + StringRef lookup( int value ) const; + }; + } // namespace Detail + + class IMutableEnumValuesRegistry { + public: + virtual ~IMutableEnumValuesRegistry(); // = default; + + virtual Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::vector<int> const& values ) = 0; + + template<typename E> + Detail::EnumInfo const& registerEnum( StringRef enumName, StringRef allEnums, std::initializer_list<E> values ) { + static_assert(sizeof(int) >= sizeof(E), "Cannot serialize enum to int"); + std::vector<int> intValues; + intValues.reserve( values.size() ); + for( auto enumValue : values ) + intValues.push_back( static_cast<int>( enumValue ) ); + return registerEnum( enumName, allEnums, intValues ); + } + }; + +} // Catch + +#endif // CATCH_INTERFACES_ENUM_VALUES_REGISTRY_HPP_INCLUDED + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW +#include <string_view> +#endif + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy{}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + // Bring in global namespace operator<< for ADL lookup in + // `IsStreamInsertable` below. + using ::operator<<; + + namespace Detail { + + inline std::size_t catch_strnlen(const char *str, std::size_t n) { + auto ret = std::char_traits<char>::find(str, n, '\0'); + if (ret != nullptr) { + return static_cast<std::size_t>(ret - str); + } + return n; + } + + constexpr StringRef unprintableString = "{?}"_sr; + + //! Encases `string in quotes, and optionally escapes invisibles + std::string convertIntoString( StringRef string, bool escapeInvisibles ); + + //! Encases `string` in quotes, and escapes invisibles if user requested + //! it via CLI + std::string convertIntoString( StringRef string ); + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template<typename T> + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + + template<typename T> + class IsStreamInsertable { + template<typename Stream, typename U> + static auto test(int) + -> decltype(std::declval<Stream&>() << std::declval<U>(), std::true_type()); + + template<typename, typename> + static auto test(...)->std::false_type; + + public: + static const bool value = decltype(test<std::ostream, const T&>(0))::value; + }; + + template<typename E> + std::string convertUnknownEnumToString( E e ); + + template<typename T> + std::enable_if_t< + !std::is_enum<T>::value && !std::is_base_of<std::exception, T>::value, + std::string> convertUnstreamable( T const& ) { + return std::string(Detail::unprintableString); + } + template<typename T> + std::enable_if_t< + !std::is_enum<T>::value && std::is_base_of<std::exception, T>::value, + std::string> convertUnstreamable(T const& ex) { + return ex.what(); + } + + + template<typename T> + std::enable_if_t< + std::is_enum<T>::value, + std::string> convertUnstreamable( T const& value ) { + return convertUnknownEnumToString( value ); + } + +#if defined(_MANAGED) + //! Convert a CLR string to a utf8 std::string + template<typename T> + std::string clrReferenceToString( T^ ref ) { + if (ref == nullptr) + return std::string("null"); + auto bytes = System::Text::Encoding::UTF8->GetBytes(ref->ToString()); + cli::pin_ptr<System::Byte> p = &bytes[0]; + return std::string(reinterpret_cast<char const *>(p), bytes->Length); + } +#endif + + } // namespace Detail + + + template <typename T, typename = void> + struct StringMaker { + template <typename Fake = T> + static + std::enable_if_t<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string> + convert(const Fake& value) { + ReusableStringStream rss; + // NB: call using the function-like syntax to avoid ambiguity with + // user-defined templated operator<< under clang. + rss.operator<<(value); + return rss.str(); + } + + template <typename Fake = T> + static + std::enable_if_t<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string> + convert( const Fake& value ) { +#if !defined(CATCH_CONFIG_FALLBACK_STRINGIFIER) + return Detail::convertUnstreamable(value); +#else + return CATCH_CONFIG_FALLBACK_STRINGIFIER(value); +#endif + } + }; + + namespace Detail { + + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template <typename T> + std::string stringify(const T& e) { + return ::Catch::StringMaker<std::remove_cv_t<std::remove_reference_t<T>>>::convert(e); + } + + template<typename E> + std::string convertUnknownEnumToString( E e ) { + return ::Catch::Detail::stringify(static_cast<std::underlying_type_t<E>>(e)); + } + +#if defined(_MANAGED) + template <typename T> + std::string stringify( T^ e ) { + return ::Catch::StringMaker<T^>::convert(e); + } +#endif + + } // namespace Detail + + // Some predefined specializations + + template<> + struct StringMaker<std::string> { + static std::string convert(const std::string& str); + }; + +#ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::string_view> { + static std::string convert(std::string_view str); + }; +#endif + + template<> + struct StringMaker<char const *> { + static std::string convert(char const * str); + }; + template<> + struct StringMaker<char *> { + static std::string convert(char * str); + }; + +#if defined(CATCH_CONFIG_WCHAR) + template<> + struct StringMaker<std::wstring> { + static std::string convert(const std::wstring& wstr); + }; + +# ifdef CATCH_CONFIG_CPP17_STRING_VIEW + template<> + struct StringMaker<std::wstring_view> { + static std::string convert(std::wstring_view str); + }; +# endif + + template<> + struct StringMaker<wchar_t const *> { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker<wchar_t *> { + static std::string convert(wchar_t * str); + }; +#endif // CATCH_CONFIG_WCHAR + + template<size_t SZ> + struct StringMaker<char[SZ]> { + static std::string convert(char const* str) { + return Detail::convertIntoString( + StringRef( str, Detail::catch_strnlen( str, SZ ) ) ); + } + }; + template<size_t SZ> + struct StringMaker<signed char[SZ]> { + static std::string convert(signed char const* str) { + auto reinterpreted = reinterpret_cast<char const*>(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + template<size_t SZ> + struct StringMaker<unsigned char[SZ]> { + static std::string convert(unsigned char const* str) { + auto reinterpreted = reinterpret_cast<char const*>(str); + return Detail::convertIntoString( + StringRef(reinterpreted, Detail::catch_strnlen(reinterpreted, SZ))); + } + }; + +#if defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker<std::byte> { + static std::string convert(std::byte value); + }; +#endif // defined(CATCH_CONFIG_CPP17_BYTE) + template<> + struct StringMaker<int> { + static std::string convert(int value); + }; + template<> + struct StringMaker<long> { + static std::string convert(long value); + }; + template<> + struct StringMaker<long long> { + static std::string convert(long long value); + }; + template<> + struct StringMaker<unsigned int> { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker<unsigned long> { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker<unsigned long long> { + static std::string convert(unsigned long long value); + }; + + template<> + struct StringMaker<bool> { + static std::string convert(bool b) { + using namespace std::string_literals; + return b ? "true"s : "false"s; + } + }; + + template<> + struct StringMaker<char> { + static std::string convert(char c); + }; + template<> + struct StringMaker<signed char> { + static std::string convert(signed char value); + }; + template<> + struct StringMaker<unsigned char> { + static std::string convert(unsigned char value); + }; + + template<> + struct StringMaker<std::nullptr_t> { + static std::string convert(std::nullptr_t) { + using namespace std::string_literals; + return "nullptr"s; + } + }; + + template<> + struct StringMaker<float> { + static std::string convert(float value); + CATCH_EXPORT static int precision; + }; + + template<> + struct StringMaker<double> { + static std::string convert(double value); + CATCH_EXPORT static int precision; + }; + + template <typename T> + struct StringMaker<T*> { + template <typename U> + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + + template <typename R, typename C> + struct StringMaker<R C::*> { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; + +#if defined(_MANAGED) + template <typename T> + struct StringMaker<T^> { + static std::string convert( T^ ref ) { + return ::Catch::Detail::clrReferenceToString(ref); + } + }; +#endif + + namespace Detail { + template<typename InputIterator, typename Sentinel = InputIterator> + std::string rangeToString(InputIterator first, Sentinel last) { + ReusableStringStream rss; + rss << "{ "; + if (first != last) { + rss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + rss << ", " << ::Catch::Detail::stringify(*first); + } + rss << " }"; + return rss.str(); + } + } + +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in their headers + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER +# define CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER +#endif + +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include <utility> +namespace Catch { + template<typename T1, typename T2> + struct StringMaker<std::pair<T1, T2> > { + static std::string convert(const std::pair<T1, T2>& pair) { + ReusableStringStream rss; + rss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_OPTIONAL) +#include <optional> +namespace Catch { + template<typename T> + struct StringMaker<std::optional<T> > { + static std::string convert(const std::optional<T>& optional) { + if (optional.has_value()) { + return ::Catch::Detail::stringify(*optional); + } else { + return "{ }"; + } + } + }; + template <> + struct StringMaker<std::nullopt_t> { + static std::string convert(const std::nullopt_t&) { + return "{ }"; + } + }; +} +#endif // CATCH_CONFIG_ENABLE_OPTIONAL_STRINGMAKER + +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include <tuple> +namespace Catch { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get<N>(tuple)); + TupleElementPrinter<Tuple, N + 1>::print(tuple, os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter<Tuple, N, false> { + static void print(const Tuple&, std::ostream&) {} + }; + + } + + + template<typename ...Types> + struct StringMaker<std::tuple<Types...>> { + static std::string convert(const std::tuple<Types...>& tuple) { + ReusableStringStream rss; + rss << '{'; + Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, rss.get()); + rss << " }"; + return rss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER + +#if defined(CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER) && defined(CATCH_CONFIG_CPP17_VARIANT) +#include <variant> +namespace Catch { + template<> + struct StringMaker<std::monostate> { + static std::string convert(const std::monostate&) { + return "{ }"; + } + }; + + template<typename... Elements> + struct StringMaker<std::variant<Elements...>> { + static std::string convert(const std::variant<Elements...>& variant) { + if (variant.valueless_by_exception()) { + return "{valueless variant}"; + } else { + return std::visit( + [](const auto& value) { + return ::Catch::Detail::stringify(value); + }, + variant + ); + } + } + }; +} +#endif // CATCH_CONFIG_ENABLE_VARIANT_STRINGMAKER + +namespace Catch { + // Import begin/ end from std here + using std::begin; + using std::end; + + namespace Detail { + template <typename T, typename = void> + struct is_range_impl : std::false_type {}; + + template <typename T> + struct is_range_impl<T, void_t<decltype(begin(std::declval<T>()))>> : std::true_type {}; + } // namespace Detail + + template <typename T> + struct is_range : Detail::is_range_impl<T> {}; + +#if defined(_MANAGED) // Managed types are never ranges + template <typename T> + struct is_range<T^> { + static const bool value = false; + }; +#endif + + template<typename Range> + std::string rangeToString( Range const& range ) { + return ::Catch::Detail::rangeToString( begin( range ), end( range ) ); + } + + // Handle vector<bool> specially + template<typename Allocator> + std::string rangeToString( std::vector<bool, Allocator> const& v ) { + ReusableStringStream rss; + rss << "{ "; + bool first = true; + for( bool b : v ) { + if( first ) + first = false; + else + rss << ", "; + rss << ::Catch::Detail::stringify( b ); + } + rss << " }"; + return rss.str(); + } + + template<typename R> + struct StringMaker<R, std::enable_if_t<is_range<R>::value && !::Catch::Detail::IsStreamInsertable<R>::value>> { + static std::string convert( R const& range ) { + return rangeToString( range ); + } + }; + + template <typename T, size_t SZ> + struct StringMaker<T[SZ]> { + static std::string convert(T const(&arr)[SZ]) { + return rangeToString(arr); + } + }; + + +} // namespace Catch + +// Separate std::chrono::duration specialization +#include <ctime> +#include <ratio> +#include <chrono> + + +namespace Catch { + +template <class Ratio> +struct ratio_string { + static std::string symbol() { + Catch::ReusableStringStream rss; + rss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return rss.str(); + } +}; + +template <> +struct ratio_string<std::atto> { + static char symbol() { return 'a'; } +}; +template <> +struct ratio_string<std::femto> { + static char symbol() { return 'f'; } +}; +template <> +struct ratio_string<std::pico> { + static char symbol() { return 'p'; } +}; +template <> +struct ratio_string<std::nano> { + static char symbol() { return 'n'; } +}; +template <> +struct ratio_string<std::micro> { + static char symbol() { return 'u'; } +}; +template <> +struct ratio_string<std::milli> { + static char symbol() { return 'm'; } +}; + + //////////// + // std::chrono::duration specializations + template<typename Value, typename Ratio> + struct StringMaker<std::chrono::duration<Value, Ratio>> { + static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { + ReusableStringStream rss; + rss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " s"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " m"; + return rss.str(); + } + }; + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { + ReusableStringStream rss; + rss << duration.count() << " h"; + return rss.str(); + } + }; + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> + template<typename Clock, typename Duration> + struct StringMaker<std::chrono::time_point<Clock, Duration>> { + static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; + } + }; + // std::chrono::time_point<system_clock> specialization + template<typename Duration> + struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { + static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + const auto err = gmtime_s(&timeInfo, &converted); + if ( err ) { + return "gmtime from provided timepoint has failed. This " + "happens e.g. with pre-1970 dates using Microsoft libc"; + } +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp, timeStampSize - 1); + } + }; +} + + +#define INTERNAL_CATCH_REGISTER_ENUM( enumName, ... ) \ +namespace Catch { \ + template<> struct StringMaker<enumName> { \ + static std::string convert( enumName value ) { \ + static const auto& enumInfo = ::Catch::getMutableRegistryHub().getMutableEnumValuesRegistry().registerEnum( #enumName, #__VA_ARGS__, { __VA_ARGS__ } ); \ + return static_cast<std::string>(enumInfo.lookup( static_cast<int>( value ) )); \ + } \ + }; \ +} + +#define CATCH_REGISTER_ENUM( enumName, ... ) INTERNAL_CATCH_REGISTER_ENUM( enumName, __VA_ARGS__ ) + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif // CATCH_TOSTRING_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { + + class Approx { + private: + bool equalityComparisonImpl(double other) const; + // Sets and validates the new margin (margin >= 0) + void setMargin(double margin); + // Sets and validates the new epsilon (0 < epsilon < 1) + void setEpsilon(double epsilon); + + public: + explicit Approx ( double value ); + + static Approx custom(); + + Approx operator-() const; + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + Approx operator()( T const& value ) const { + Approx approx( static_cast<double>(value) ); + approx.m_epsilon = m_epsilon; + approx.m_margin = m_margin; + approx.m_scale = m_scale; + return approx; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + explicit Approx( T const& value ): Approx(static_cast<double>(value)) + {} + + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast<double>(lhs); + return rhs.equalityComparisonImpl(lhs_v); + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator == ( Approx const& lhs, const T& rhs ) { + return operator==( rhs, lhs ); + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator != ( T const& lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator != ( Approx const& lhs, T const& rhs ) { + return !operator==( rhs, lhs ); + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + Approx& epsilon( T const& newEpsilon ) { + const auto epsilonAsDouble = static_cast<double>(newEpsilon); + setEpsilon(epsilonAsDouble); + return *this; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + Approx& margin( T const& newMargin ) { + const auto marginAsDouble = static_cast<double>(newMargin); + setMargin(marginAsDouble); + return *this; + } + + template <typename T, typename = std::enable_if_t<std::is_constructible<double, T>::value>> + Approx& scale( T const& newScale ) { + m_scale = static_cast<double>(newScale); + return *this; + } + + std::string toString() const; + + private: + double m_epsilon; + double m_margin; + double m_scale; + double m_value; + }; + +namespace literals { + Approx operator ""_a(long double val); + Approx operator ""_a(unsigned long long val); +} // end namespace literals + +template<> +struct StringMaker<Catch::Approx> { + static std::string convert(Catch::Approx const& value); +}; + +} // end namespace Catch + +#endif // CATCH_APPROX_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_INFO_HPP_INCLUDED +#define CATCH_ASSERTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_SOURCE_LINE_INFO_HPP_INCLUDED +#define CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +#include <cstddef> +#include <iosfwd> + +namespace Catch { + + struct SourceLineInfo { + + SourceLineInfo() = delete; + constexpr SourceLineInfo( char const* _file, std::size_t _line ) noexcept: + file( _file ), + line( _line ) + {} + + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + + friend std::ostream& operator << (std::ostream& os, SourceLineInfo const& info); + }; +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) + +#endif // CATCH_SOURCE_LINE_INFO_HPP_INCLUDED + +namespace Catch { + + struct AssertionInfo { + // AssertionInfo() = delete; + + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_ASSERTION_RESULT_HPP_INCLUDED +#define CATCH_ASSERTION_RESULT_HPP_INCLUDED + + + +#ifndef CATCH_LAZY_EXPR_HPP_INCLUDED +#define CATCH_LAZY_EXPR_HPP_INCLUDED + +#include <iosfwd> + +namespace Catch { + + class ITransientExpression; + + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; + friend class RunContext; + + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + constexpr LazyExpression( bool isNegated ): + m_isNegated(isNegated) + {} + constexpr LazyExpression(LazyExpression const& other) = default; + LazyExpression& operator = ( LazyExpression const& ) = delete; + + constexpr explicit operator bool() const { + return m_transientExpression != nullptr; + } + + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; + +} // namespace Catch + +#endif // CATCH_LAZY_EXPR_HPP_INCLUDED + +#include <string> + +namespace Catch { + + struct AssertionResultData + { + AssertionResultData() = delete; + + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); + + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; + + std::string reconstructExpression() const; + }; + + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData&& data ); + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + StringRef getMessage() const; + SourceLineInfo getSourceInfo() const; + StringRef getTestMacroName() const; + + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +#endif // CATCH_ASSERTION_RESULT_HPP_INCLUDED + + +#ifndef CATCH_CASE_SENSITIVE_HPP_INCLUDED +#define CATCH_CASE_SENSITIVE_HPP_INCLUDED + +namespace Catch { + + enum class CaseSensitive { Yes, No }; + +} // namespace Catch + +#endif // CATCH_CASE_SENSITIVE_HPP_INCLUDED + + +#ifndef CATCH_CONFIG_HPP_INCLUDED +#define CATCH_CONFIG_HPP_INCLUDED + + + +#ifndef CATCH_TEST_SPEC_HPP_INCLUDED +#define CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + + +#ifndef CATCH_WILDCARD_PATTERN_HPP_INCLUDED +#define CATCH_WILDCARD_PATTERN_HPP_INCLUDED + + +#include <string> + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive caseSensitivity ); + bool matches( std::string const& str ) const; + + private: + std::string normaliseString( std::string const& str ) const; + CaseSensitive m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +#endif // CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +#include <iosfwd> +#include <string> +#include <vector> + +namespace Catch { + + class IConfig; + struct TestCaseInfo; + class TestCaseHandle; + + class TestSpec { + + class Pattern { + public: + explicit Pattern( std::string const& name ); + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + std::string const& name() const; + private: + virtual void serializeTo( std::ostream& out ) const = 0; + // Writes string that would be reparsed into the pattern + friend std::ostream& operator<<(std::ostream& out, + Pattern const& pattern) { + pattern.serializeTo( out ); + return out; + } + + std::string const m_name; + }; + + class NamePattern : public Pattern { + public: + explicit NamePattern( std::string const& name, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + WildcardPattern m_wildcardPattern; + }; + + class TagPattern : public Pattern { + public: + explicit TagPattern( std::string const& tag, std::string const& filterString ); + bool matches( TestCaseInfo const& testCase ) const override; + private: + void serializeTo( std::ostream& out ) const override; + + std::string m_tag; + }; + + struct Filter { + std::vector<Detail::unique_ptr<Pattern>> m_required; + std::vector<Detail::unique_ptr<Pattern>> m_forbidden; + + //! Serializes this filter into a string that would be parsed into + //! an equivalent filter + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, Filter const& f) { + f.serializeTo( out ); + return out; + } + + bool matches( TestCaseInfo const& testCase ) const; + }; + + static std::string extractFilterName( Filter const& filter ); + + public: + struct FilterMatch { + std::string name; + std::vector<TestCaseHandle const*> tests; + }; + using Matches = std::vector<FilterMatch>; + using vectorStrings = std::vector<std::string>; + + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; + Matches matchesByFilter( std::vector<TestCaseHandle> const& testCases, IConfig const& config ) const; + const vectorStrings & getInvalidSpecs() const; + + private: + std::vector<Filter> m_filters; + std::vector<std::string> m_invalidSpecs; + + friend class TestSpecParser; + //! Serializes this test spec into a string that would be parsed into + //! equivalent test spec + void serializeTo( std::ostream& out ) const; + friend std::ostream& operator<<(std::ostream& out, + TestSpec const& spec) { + spec.serializeTo( out ); + return out; + } + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_HPP_INCLUDED + + +#ifndef CATCH_OPTIONAL_HPP_INCLUDED +#define CATCH_OPTIONAL_HPP_INCLUDED + + +#include <cassert> + +namespace Catch { + + // An optional type + template<typename T> + class Optional { + public: + Optional(): nullableValue( nullptr ) {} + ~Optional() { reset(); } + + Optional( T const& _value ): + nullableValue( new ( storage ) T( _value ) ) {} + Optional( T&& _value ): + nullableValue( new ( storage ) T( CATCH_MOVE( _value ) ) ) {} + + Optional& operator=( T const& _value ) { + reset(); + nullableValue = new ( storage ) T( _value ); + return *this; + } + Optional& operator=( T&& _value ) { + reset(); + nullableValue = new ( storage ) T( CATCH_MOVE( _value ) ); + return *this; + } + + Optional( Optional const& _other ): + nullableValue( _other ? new ( storage ) T( *_other ) : nullptr ) {} + Optional( Optional&& _other ): + nullableValue( _other ? new ( storage ) T( CATCH_MOVE( *_other ) ) + : nullptr ) {} + + Optional& operator=( Optional const& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { nullableValue = new ( storage ) T( *_other ); } + } + return *this; + } + Optional& operator=( Optional&& _other ) { + if ( &_other != this ) { + reset(); + if ( _other ) { + nullableValue = new ( storage ) T( CATCH_MOVE( *_other ) ); + } + } + return *this; + } + + void reset() { + if ( nullableValue ) { nullableValue->~T(); } + nullableValue = nullptr; + } + + T& operator*() { + assert(nullableValue); + return *nullableValue; + } + T const& operator*() const { + assert(nullableValue); + return *nullableValue; + } + T* operator->() { + assert(nullableValue); + return nullableValue; + } + const T* operator->() const { + assert(nullableValue); + return nullableValue; + } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); + } + + friend bool operator==(Optional const& a, Optional const& b) { + if (a.none() && b.none()) { + return true; + } else if (a.some() && b.some()) { + return *a == *b; + } else { + return false; + } + } + friend bool operator!=(Optional const& a, Optional const& b) { + return !( a == b ); + } + + private: + T* nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; + }; + +} // end namespace Catch + +#endif // CATCH_OPTIONAL_HPP_INCLUDED + + +#ifndef CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED +#define CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + enum class GenerateFrom { + Time, + RandomDevice, + //! Currently equivalent to RandomDevice, but can change at any point + Default + }; + + std::uint32_t generateRandomSeed(GenerateFrom from); + +} // end namespace Catch + +#endif // CATCH_RANDOM_SEED_GENERATION_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED +#define CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + + +#include <map> +#include <string> +#include <vector> + +namespace Catch { + + enum class ColourMode : std::uint8_t; + + namespace Detail { + //! Splits the reporter spec into reporter name and kv-pair options + std::vector<std::string> splitReporterSpec( StringRef reporterSpec ); + + Optional<ColourMode> stringToColourMode( StringRef colourMode ); + } + + /** + * Structured reporter spec that a reporter can be created from + * + * Parsing has been validated, but semantics have not. This means e.g. + * that the colour mode is known to Catch2, but it might not be + * compiled into the binary, and the output filename might not be + * openable. + */ + class ReporterSpec { + std::string m_name; + Optional<std::string> m_outputFileName; + Optional<ColourMode> m_colourMode; + std::map<std::string, std::string> m_customOptions; + + friend bool operator==( ReporterSpec const& lhs, + ReporterSpec const& rhs ); + friend bool operator!=( ReporterSpec const& lhs, + ReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + + public: + ReporterSpec( + std::string name, + Optional<std::string> outputFileName, + Optional<ColourMode> colourMode, + std::map<std::string, std::string> customOptions ); + + std::string const& name() const { return m_name; } + + Optional<std::string> const& outputFile() const { + return m_outputFileName; + } + + Optional<ColourMode> const& colourMode() const { return m_colourMode; } + + std::map<std::string, std::string> const& customOptions() const { + return m_customOptions; + } + }; + + /** + * Parses provided reporter spec string into + * + * Returns empty optional on errors, e.g. + * * field that is not first and not a key+value pair + * * duplicated keys in kv pair + * * unknown catch reporter option + * * empty key/value in an custom kv pair + * * ... + */ + Optional<ReporterSpec> parseReporterSpec( StringRef reporterSpec ); + +} + +#endif // CATCH_REPORTER_SPEC_PARSER_HPP_INCLUDED + +#include <chrono> +#include <map> +#include <string> +#include <vector> + +namespace Catch { + + class IStream; + + /** + * `ReporterSpec` but with the defaults filled in. + * + * Like `ReporterSpec`, the semantics are unchecked. + */ + struct ProcessedReporterSpec { + std::string name; + std::string outputFilename; + ColourMode colourMode; + std::map<std::string, std::string> customOptions; + friend bool operator==( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ); + friend bool operator!=( ProcessedReporterSpec const& lhs, + ProcessedReporterSpec const& rhs ) { + return !( lhs == rhs ); + } + }; + + struct ConfigData { + + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listListeners = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + bool allowZeroTests = false; + + int abortAfter = -1; + uint32_t rngSeed = generateRandomSeed(GenerateFrom::Default); + + unsigned int shardCount = 1; + unsigned int shardIndex = 0; + + bool skipBenchmarks = false; + bool benchmarkNoAnalysis = false; + unsigned int benchmarkSamples = 100; + double benchmarkConfidenceInterval = 0.95; + unsigned int benchmarkResamples = 100'000; + std::chrono::milliseconds::rep benchmarkWarmupTime = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations showDurations = ShowDurations::DefaultForReporter; + double minDuration = -1; + TestRunOrder runOrder = TestRunOrder::Declared; + ColourMode defaultColourMode = ColourMode::PlatformDefault; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; + + std::string defaultOutputFilename; + std::string name; + std::string processName; + std::vector<ReporterSpec> reporterSpecifications; + + std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; + }; + + + class Config : public IConfig { + public: + + Config() = default; + Config( ConfigData const& data ); + ~Config() override; // = default in the cpp file + + bool listTests() const; + bool listTags() const; + bool listReporters() const; + bool listListeners() const; + + std::vector<ReporterSpec> const& getReporterSpecs() const; + std::vector<ProcessedReporterSpec> const& + getProcessedReporterSpecs() const; + + std::vector<std::string> const& getTestsOrTags() const override; + std::vector<std::string> const& getSectionsToRun() const override; + + TestSpec const& testSpec() const override; + bool hasTestFilters() const override; + + bool showHelp() const; + + // IConfig interface + bool allowThrows() const override; + StringRef name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + bool warnAboutUnmatchedTestSpecs() const override; + bool zeroTestsCountAsSuccess() const override; + ShowDurations showDurations() const override; + double minDuration() const override; + TestRunOrder runOrder() const override; + uint32_t rngSeed() const override; + unsigned int shardCount() const override; + unsigned int shardIndex() const override; + ColourMode defaultColourMode() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; + bool skipBenchmarks() const override; + bool benchmarkNoAnalysis() const override; + unsigned int benchmarkSamples() const override; + double benchmarkConfidenceInterval() const override; + unsigned int benchmarkResamples() const override; + std::chrono::milliseconds benchmarkWarmupTime() const override; + + private: + // Reads Bazel env vars and applies them to the config + void readBazelEnvVars(); + + ConfigData m_data; + std::vector<ProcessedReporterSpec> m_processedReporterSpecs; + TestSpec m_testSpec; + bool m_hasTestFilters = false; + }; +} // end namespace Catch + +#endif // CATCH_CONFIG_HPP_INCLUDED + + +#ifndef CATCH_GET_RANDOM_SEED_HPP_INCLUDED +#define CATCH_GET_RANDOM_SEED_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + //! Returns Catch2's current RNG seed. + std::uint32_t getSeed(); +} + +#endif // CATCH_GET_RANDOM_SEED_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_HPP_INCLUDED +#define CATCH_MESSAGE_HPP_INCLUDED + + + + +/** \file + * Wrapper for the CATCH_CONFIG_PREFIX_MESSAGES configuration option + * + * CATCH_CONFIG_PREFIX_ALL can be used to avoid clashes with other macros + * by prepending CATCH_. This may not be desirable if the only clashes are with + * logger macros such as INFO and WARN. In this cases + * CATCH_CONFIG_PREFIX_MESSAGES can be used to only prefix a small subset + * of relevant macros. + * + */ + +#ifndef CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED +#define CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_PREFIX_MESSAGES) + #define CATCH_CONFIG_PREFIX_MESSAGES +#endif + +#endif // CATCH_CONFIG_PREFIX_MESSAGES_HPP_INCLUDED + + +#ifndef CATCH_STREAM_END_STOP_HPP_INCLUDED +#define CATCH_STREAM_END_STOP_HPP_INCLUDED + + +namespace Catch { + + // Use this in variadic streaming macros to allow + // << +StreamEndStop + // as well as + // << stuff +StreamEndStop + struct StreamEndStop { + constexpr StringRef operator+() const { return StringRef(); } + + template <typename T> + constexpr friend T const& operator+( T const& value, StreamEndStop ) { + return value; + } + }; + +} // namespace Catch + +#endif // CATCH_STREAM_END_STOP_HPP_INCLUDED + + +#ifndef CATCH_MESSAGE_INFO_HPP_INCLUDED +#define CATCH_MESSAGE_INFO_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + struct MessageInfo { + MessageInfo( StringRef _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + StringRef macroName; + std::string message; + SourceLineInfo lineInfo; + ResultWas::OfType type; + unsigned int sequence; + + bool operator == (MessageInfo const& other) const { + return sequence == other.sequence; + } + bool operator < (MessageInfo const& other) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + +} // end namespace Catch + +#endif // CATCH_MESSAGE_INFO_HPP_INCLUDED + +#include <string> +#include <vector> + +namespace Catch { + + struct SourceLineInfo; + class IResultCapture; + + struct MessageStream { + + template<typename T> + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + ReusableStringStream m_stream; + }; + + struct MessageBuilder : MessageStream { + MessageBuilder( StringRef macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ): + m_info(macroName, lineInfo, type) {} + + template<typename T> + MessageBuilder&& operator << ( T const& value ) && { + m_stream << value; + return CATCH_MOVE(*this); + } + + MessageInfo m_info; + }; + + class ScopedMessage { + public: + explicit ScopedMessage( MessageBuilder&& builder ); + ScopedMessage( ScopedMessage& duplicate ) = delete; + ScopedMessage( ScopedMessage&& old ) noexcept; + ~ScopedMessage(); + + MessageInfo m_info; + bool m_moved = false; + }; + + class Capturer { + std::vector<MessageInfo> m_messages; + IResultCapture& m_resultCapture; + size_t m_captured = 0; + public: + Capturer( StringRef macroName, SourceLineInfo const& lineInfo, ResultWas::OfType resultType, StringRef names ); + + Capturer(Capturer const&) = delete; + Capturer& operator=(Capturer const&) = delete; + + ~Capturer(); + + void captureValue( size_t index, std::string const& value ); + + template<typename T> + void captureValues( size_t index, T const& value ) { + captureValue( index, Catch::Detail::stringify( value ) ); + } + + template<typename T, typename... Ts> + void captureValues( size_t index, T const& value, Ts const&... values ) { + captureValue( index, Catch::Detail::stringify(value) ); + captureValues( index+1, values... ); + } + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::StringRef(), resultDisposition ); \ + catchAssertionHandler.handleMessage( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + catchAssertionHandler.complete(); \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_CAPTURE( varName, macroName, ... ) \ + Catch::Capturer varName( macroName##_catch_sr, \ + CATCH_INTERNAL_LINEINFO, \ + Catch::ResultWas::Info, \ + #__VA_ARGS__##_catch_sr ); \ + varName.captureValues( 0, __VA_ARGS__ ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( macroName, log ) \ + const Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage )( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_UNSCOPED_INFO( macroName, log ) \ + Catch::getResultCapture().emplaceUnscopedMessage( Catch::MessageBuilder( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log ) + + +#if defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) + #define CATCH_UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "CATCH_UNSCOPED_INFO", msg ) + #define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CATCH_CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CATCH_CAPTURE", __VA_ARGS__ ) + +#elif defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_INFO( msg ) (void)(0) + #define CATCH_UNSCOPED_INFO( msg ) (void)(0) + #define CATCH_WARN( msg ) (void)(0) + #define CATCH_CAPTURE( ... ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && !defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) + #define UNSCOPED_INFO( msg ) INTERNAL_CATCH_UNSCOPED_INFO( "UNSCOPED_INFO", msg ) + #define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) + #define CAPTURE( ... ) INTERNAL_CATCH_CAPTURE( INTERNAL_CATCH_UNIQUE_NAME(capturer), "CAPTURE", __VA_ARGS__ ) + +#elif !defined(CATCH_CONFIG_PREFIX_MESSAGES) && defined(CATCH_CONFIG_DISABLE) + + #define INFO( msg ) (void)(0) + #define UNSCOPED_INFO( msg ) (void)(0) + #define WARN( msg ) (void)(0) + #define CAPTURE( ... ) (void)(0) + +#endif // end of user facing macro declarations + + + + +#endif // CATCH_MESSAGE_HPP_INCLUDED + + +#ifndef CATCH_SECTION_INFO_HPP_INCLUDED +#define CATCH_SECTION_INFO_HPP_INCLUDED + + + +#ifndef CATCH_TOTALS_HPP_INCLUDED +#define CATCH_TOTALS_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + struct Counts { + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); + + std::uint64_t total() const; + bool allPassed() const; + bool allOk() const; + + std::uint64_t passed = 0; + std::uint64_t failed = 0; + std::uint64_t failedButOk = 0; + std::uint64_t skipped = 0; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); + + Totals delta( Totals const& prevTotals ) const; + + Counts assertions; + Counts testCases; + }; +} + +#endif // CATCH_TOTALS_HPP_INCLUDED + +#include <string> + +namespace Catch { + + struct SectionInfo { + // The last argument is ignored, so that people can write + // SECTION("ShortName", "Proper description that is long") and + // still use the `-c` flag comfortably. + SectionInfo( SourceLineInfo const& _lineInfo, std::string _name, + const char* const = nullptr ): + name(CATCH_MOVE(_name)), + lineInfo(_lineInfo) + {} + + std::string name; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +#endif // CATCH_SECTION_INFO_HPP_INCLUDED + + +#ifndef CATCH_SESSION_HPP_INCLUDED +#define CATCH_SESSION_HPP_INCLUDED + + + +#ifndef CATCH_COMMANDLINE_HPP_INCLUDED +#define CATCH_COMMANDLINE_HPP_INCLUDED + + + +#ifndef CATCH_CLARA_HPP_INCLUDED +#define CATCH_CLARA_HPP_INCLUDED + +#if defined( __clang__ ) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wweak-vtables" +# pragma clang diagnostic ignored "-Wshadow" +# pragma clang diagnostic ignored "-Wdeprecated" +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +#ifndef CLARA_CONFIG_OPTIONAL_TYPE +# ifdef __has_include +# if __has_include( <optional>) && __cplusplus >= 201703L +# include <optional> +# define CLARA_CONFIG_OPTIONAL_TYPE std::optional +# endif +# endif +#endif + + +#include <cassert> +#include <memory> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> +#include <vector> + +namespace Catch { + namespace Clara { + + class Args; + class Parser; + + // enum of result types from a parse + enum class ParseResultType { + Matched, + NoMatch, + ShortCircuitAll, + ShortCircuitSame + }; + + struct accept_many_t {}; + constexpr accept_many_t accept_many {}; + + namespace Detail { + struct fake_arg { + template <typename T> + operator T(); + }; + + template <typename F, typename = void> + struct is_unary_function : std::false_type {}; + + template <typename F> + struct is_unary_function< + F, + Catch::Detail::void_t<decltype( + std::declval<F>()( fake_arg() ) ) + > + > : std::true_type {}; + + // Traits for extracting arg and return type of lambdas (for single + // argument lambdas) + template <typename L> + struct UnaryLambdaTraits + : UnaryLambdaTraits<decltype( &L::operator() )> {}; + + template <typename ClassT, typename ReturnT, typename... Args> + struct UnaryLambdaTraits<ReturnT ( ClassT::* )( Args... ) const> { + static const bool isValid = false; + }; + + template <typename ClassT, typename ReturnT, typename ArgT> + struct UnaryLambdaTraits<ReturnT ( ClassT::* )( ArgT ) const> { + static const bool isValid = true; + using ArgType = std::remove_const_t<std::remove_reference_t<ArgT>>; + using ReturnType = ReturnT; + }; + + class TokenStream; + + // Wraps a token coming from a token stream. These may not directly + // correspond to strings as a single string may encode an option + + // its argument if the : or = form is used + enum class TokenType { Option, Argument }; + struct Token { + TokenType type; + StringRef token; + }; + + // Abstracts iterators into args as a stream of tokens, with option + // arguments uniformly handled + class TokenStream { + using Iterator = std::vector<StringRef>::const_iterator; + Iterator it; + Iterator itEnd; + std::vector<Token> m_tokenBuffer; + void loadBuffer(); + + public: + explicit TokenStream( Args const& args ); + TokenStream( Iterator it, Iterator itEnd ); + + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; + } + + size_t count() const { + return m_tokenBuffer.size() + ( itEnd - it ); + } + + Token operator*() const { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); + } + + Token const* operator->() const { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); + } + + TokenStream& operator++(); + }; + + //! Denotes type of a parsing result + enum class ResultType { + Ok, ///< No errors + LogicError, ///< Error in user-specified arguments for + ///< construction + RuntimeError ///< Error in parsing inputs + }; + + class ResultBase { + protected: + ResultBase( ResultType type ): m_type( type ) {} + virtual ~ResultBase(); // = default; + + + ResultBase(ResultBase const&) = default; + ResultBase& operator=(ResultBase const&) = default; + ResultBase(ResultBase&&) = default; + ResultBase& operator=(ResultBase&&) = default; + + virtual void enforceOk() const = 0; + + ResultType m_type; + }; + + template <typename T> + class ResultValueBase : public ResultBase { + public: + T const& value() const& { + enforceOk(); + return m_value; + } + T&& value() && { + enforceOk(); + return CATCH_MOVE( m_value ); + } + + protected: + ResultValueBase( ResultType type ): ResultBase( type ) {} + + ResultValueBase( ResultValueBase const& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + } + ResultValueBase( ResultValueBase&& other ): + ResultBase( other ) { + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + } + + + ResultValueBase( ResultType, T const& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( value ); + } + ResultValueBase( ResultType, T&& value ): + ResultBase( ResultType::Ok ) { + new ( &m_value ) T( CATCH_MOVE(value) ); + } + + ResultValueBase& operator=( ResultValueBase const& other ) { + if ( m_type == ResultType::Ok ) + m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( other.m_value ); + return *this; + } + ResultValueBase& operator=( ResultValueBase&& other ) { + if ( m_type == ResultType::Ok ) m_value.~T(); + ResultBase::operator=( other ); + if ( m_type == ResultType::Ok ) + new ( &m_value ) T( CATCH_MOVE(other.m_value) ); + return *this; + } + + + ~ResultValueBase() override { + if ( m_type == ResultType::Ok ) + m_value.~T(); + } + + union { + T m_value; + }; + }; + + template <> class ResultValueBase<void> : public ResultBase { + protected: + using ResultBase::ResultBase; + }; + + template <typename T = void> + class BasicResult : public ResultValueBase<T> { + public: + template <typename U> + explicit BasicResult( BasicResult<U> const& other ): + ResultValueBase<T>( other.type() ), + m_errorMessage( other.errorMessage() ) { + assert( type() != ResultType::Ok ); + } + + template <typename U> + static auto ok( U&& value ) -> BasicResult { + return { ResultType::Ok, CATCH_FORWARD(value) }; + } + static auto ok() -> BasicResult { return { ResultType::Ok }; } + static auto logicError( std::string&& message ) + -> BasicResult { + return { ResultType::LogicError, CATCH_MOVE(message) }; + } + static auto runtimeError( std::string&& message ) + -> BasicResult { + return { ResultType::RuntimeError, CATCH_MOVE(message) }; + } + + explicit operator bool() const { + return m_type == ResultType::Ok; + } + auto type() const -> ResultType { return m_type; } + auto errorMessage() const -> std::string const& { + return m_errorMessage; + } + + protected: + void enforceOk() const override { + + // Errors shouldn't reach this point, but if they do + // the actual error message will be in m_errorMessage + assert( m_type != ResultType::LogicError ); + assert( m_type != ResultType::RuntimeError ); + if ( m_type != ResultType::Ok ) + std::abort(); + } + + std::string + m_errorMessage; // Only populated if resultType is an error + + BasicResult( ResultType type, + std::string&& message ): + ResultValueBase<T>( type ), m_errorMessage( CATCH_MOVE(message) ) { + assert( m_type != ResultType::Ok ); + } + + using ResultValueBase<T>::ResultValueBase; + using ResultBase::m_type; + }; + + class ParseState { + public: + ParseState( ParseResultType type, + TokenStream remainingTokens ); + + ParseResultType type() const { return m_type; } + TokenStream const& remainingTokens() const& { + return m_remainingTokens; + } + TokenStream&& remainingTokens() && { + return CATCH_MOVE( m_remainingTokens ); + } + + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult<void>; + using ParserResult = BasicResult<ParseResultType>; + using InternalParseResult = BasicResult<ParseState>; + + struct HelpColumns { + std::string left; + StringRef descriptions; + }; + + template <typename T> + ParserResult convertInto( std::string const& source, T& target ) { + std::stringstream ss( source ); + ss >> target; + if ( ss.fail() ) { + return ParserResult::runtimeError( + "Unable to convert '" + source + + "' to destination type" ); + } else { + return ParserResult::ok( ParseResultType::Matched ); + } + } + ParserResult convertInto( std::string const& source, + std::string& target ); + ParserResult convertInto( std::string const& source, bool& target ); + +#ifdef CLARA_CONFIG_OPTIONAL_TYPE + template <typename T> + auto convertInto( std::string const& source, + CLARA_CONFIG_OPTIONAL_TYPE<T>& target ) + -> ParserResult { + T temp; + auto result = convertInto( source, temp ); + if ( result ) + target = CATCH_MOVE( temp ); + return result; + } +#endif // CLARA_CONFIG_OPTIONAL_TYPE + + struct BoundRef : Catch::Detail::NonCopyable { + virtual ~BoundRef() = default; + virtual bool isContainer() const; + virtual bool isFlag() const; + }; + struct BoundValueRefBase : BoundRef { + virtual auto setValue( std::string const& arg ) + -> ParserResult = 0; + }; + struct BoundFlagRefBase : BoundRef { + virtual auto setFlag( bool flag ) -> ParserResult = 0; + bool isFlag() const override; + }; + + template <typename T> struct BoundValueRef : BoundValueRefBase { + T& m_ref; + + explicit BoundValueRef( T& ref ): m_ref( ref ) {} + + ParserResult setValue( std::string const& arg ) override { + return convertInto( arg, m_ref ); + } + }; + + template <typename T> + struct BoundValueRef<std::vector<T>> : BoundValueRefBase { + std::vector<T>& m_ref; + + explicit BoundValueRef( std::vector<T>& ref ): m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const& arg ) + -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if ( result ) + m_ref.push_back( temp ); + return result; + } + }; + + struct BoundFlagRef : BoundFlagRefBase { + bool& m_ref; + + explicit BoundFlagRef( bool& ref ): m_ref( ref ) {} + + ParserResult setFlag( bool flag ) override; + }; + + template <typename ReturnType> struct LambdaInvoker { + static_assert( + std::is_same<ReturnType, ParserResult>::value, + "Lambda must return void or clara::ParserResult" ); + + template <typename L, typename ArgType> + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + return lambda( arg ); + } + }; + + template <> struct LambdaInvoker<void> { + template <typename L, typename ArgType> + static auto invoke( L const& lambda, ArgType const& arg ) + -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); + } + }; + + template <typename ArgType, typename L> + auto invokeLambda( L const& lambda, std::string const& arg ) + -> ParserResult { + ArgType temp{}; + auto result = convertInto( arg, temp ); + return !result ? result + : LambdaInvoker<typename UnaryLambdaTraits< + L>::ReturnType>::invoke( lambda, temp ); + } + + template <typename L> struct BoundLambda : BoundValueRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits<L>::isValid, + "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const& lambda ): m_lambda( lambda ) {} + + auto setValue( std::string const& arg ) + -> ParserResult override { + return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( + m_lambda, arg ); + } + }; + + template <typename L> struct BoundManyLambda : BoundLambda<L> { + explicit BoundManyLambda( L const& lambda ): BoundLambda<L>( lambda ) {} + bool isContainer() const override { return true; } + }; + + template <typename L> struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; + + static_assert( + UnaryLambdaTraits<L>::isValid, + "Supplied lambda must take exactly one argument" ); + static_assert( + std::is_same<typename UnaryLambdaTraits<L>::ArgType, + bool>::value, + "flags must be boolean" ); + + explicit BoundFlagLambda( L const& lambda ): + m_lambda( lambda ) {} + + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker<typename UnaryLambdaTraits< + L>::ReturnType>::invoke( m_lambda, flag ); + } + }; + + enum class Optionality { Optional, Required }; + + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, + TokenStream tokens ) const + -> InternalParseResult = 0; + virtual size_t cardinality() const; + + InternalParseResult parse( Args const& args ) const; + }; + + template <typename DerivedT> + class ComposableParserImpl : public ParserBase { + public: + template <typename T> + auto operator|( T const& other ) const -> Parser; + }; + + // Common code and state for Args and Opts + template <typename DerivedT> + class ParserRefImpl : public ComposableParserImpl<DerivedT> { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr<BoundRef> m_ref; + StringRef m_hint; + StringRef m_description; + + explicit ParserRefImpl( std::shared_ptr<BoundRef> const& ref ): + m_ref( ref ) {} + + public: + template <typename LambdaT> + ParserRefImpl( accept_many_t, + LambdaT const& ref, + StringRef hint ): + m_ref( std::make_shared<BoundManyLambda<LambdaT>>( ref ) ), + m_hint( hint ) {} + + template <typename T, + typename = typename std::enable_if_t< + !Detail::is_unary_function<T>::value>> + ParserRefImpl( T& ref, StringRef hint ): + m_ref( std::make_shared<BoundValueRef<T>>( ref ) ), + m_hint( hint ) {} + + template <typename LambdaT, + typename = typename std::enable_if_t< + Detail::is_unary_function<LambdaT>::value>> + ParserRefImpl( LambdaT const& ref, StringRef hint ): + m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), + m_hint( hint ) {} + + DerivedT& operator()( StringRef description ) & { + m_description = description; + return static_cast<DerivedT&>( *this ); + } + DerivedT&& operator()( StringRef description ) && { + m_description = description; + return static_cast<DerivedT&&>( *this ); + } + + auto optional() -> DerivedT& { + m_optionality = Optionality::Optional; + return static_cast<DerivedT&>( *this ); + } + + auto required() -> DerivedT& { + m_optionality = Optionality::Required; + return static_cast<DerivedT&>( *this ); + } + + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } + + auto cardinality() const -> size_t override { + if ( m_ref->isContainer() ) + return 0; + else + return 1; + } + + StringRef hint() const { return m_hint; } + }; + + } // namespace detail + + + // A parser for arguments + class Arg : public Detail::ParserRefImpl<Arg> { + public: + using ParserRefImpl::ParserRefImpl; + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + }; + + // A parser for options + class Opt : public Detail::ParserRefImpl<Opt> { + protected: + std::vector<StringRef> m_optNames; + + public: + template <typename LambdaT> + explicit Opt(LambdaT const& ref) : + ParserRefImpl( + std::make_shared<Detail::BoundFlagLambda<LambdaT>>(ref)) {} + + explicit Opt(bool& ref); + + template <typename LambdaT, + typename = typename std::enable_if_t< + Detail::is_unary_function<LambdaT>::value>> + Opt( LambdaT const& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + template <typename LambdaT> + Opt( accept_many_t, LambdaT const& ref, StringRef hint ): + ParserRefImpl( accept_many, ref, hint ) {} + + template <typename T, + typename = typename std::enable_if_t< + !Detail::is_unary_function<T>::value>> + Opt( T& ref, StringRef hint ): + ParserRefImpl( ref, hint ) {} + + Opt& operator[]( StringRef optName ) & { + m_optNames.push_back(optName); + return *this; + } + Opt&& operator[]( StringRef optName ) && { + m_optNames.push_back( optName ); + return CATCH_MOVE(*this); + } + + Detail::HelpColumns getHelpColumns() const; + + bool isMatch(StringRef optToken) const; + + using ParserBase::parse; + + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + Detail::Result validate() const override; + }; + + // Specifies the name of the executable + class ExeName : public Detail::ComposableParserImpl<ExeName> { + std::shared_ptr<std::string> m_name; + std::shared_ptr<Detail::BoundValueRefBase> m_ref; + + public: + ExeName(); + explicit ExeName(std::string& ref); + + template <typename LambdaT> + explicit ExeName(LambdaT const& lambda) : ExeName() { + m_ref = std::make_shared<Detail::BoundLambda<LambdaT>>(lambda); + } + + // The exe name is not parsed out of the normal tokens, but is + // handled specially + Detail::InternalParseResult + parse(std::string const&, + Detail::TokenStream tokens) const override; + + std::string const& name() const { return *m_name; } + Detail::ParserResult set(std::string const& newName); + }; + + + // A Combined parser + class Parser : Detail::ParserBase { + mutable ExeName m_exeName; + std::vector<Opt> m_options; + std::vector<Arg> m_args; + + public: + + auto operator|=(ExeName const& exeName) -> Parser& { + m_exeName = exeName; + return *this; + } + + auto operator|=(Arg const& arg) -> Parser& { + m_args.push_back(arg); + return *this; + } + + friend Parser& operator|=( Parser& p, Opt const& opt ) { + p.m_options.push_back( opt ); + return p; + } + friend Parser& operator|=( Parser& p, Opt&& opt ) { + p.m_options.push_back( CATCH_MOVE(opt) ); + return p; + } + + Parser& operator|=(Parser const& other); + + template <typename T> + friend Parser operator|( Parser const& p, T&& rhs ) { + Parser temp( p ); + temp |= rhs; + return temp; + } + + template <typename T> + friend Parser operator|( Parser&& p, T&& rhs ) { + p |= CATCH_FORWARD(rhs); + return CATCH_MOVE(p); + } + + std::vector<Detail::HelpColumns> getHelpColumns() const; + + void writeToStream(std::ostream& os) const; + + friend auto operator<<(std::ostream& os, Parser const& parser) + -> std::ostream& { + parser.writeToStream(os); + return os; + } + + Detail::Result validate() const override; + + using ParserBase::parse; + Detail::InternalParseResult + parse(std::string const& exeName, + Detail::TokenStream tokens) const override; + }; + + /** + * Wrapper over argc + argv, assumes that the inputs outlive it + */ + class Args { + friend Detail::TokenStream; + StringRef m_exeName; + std::vector<StringRef> m_args; + + public: + Args(int argc, char const* const* argv); + // Helper constructor for testing + Args(std::initializer_list<StringRef> args); + + StringRef exeName() const { return m_exeName; } + }; + + + // Convenience wrapper for option parser that specifies the help option + struct Help : Opt { + Help(bool& showHelpFlag); + }; + + // Result type for parser operation + using Detail::ParserResult; + + namespace Detail { + template <typename DerivedT> + template <typename T> + Parser + ComposableParserImpl<DerivedT>::operator|(T const& other) const { + return Parser() | static_cast<DerivedT const&>(*this) | other; + } + } + + } // namespace Clara +} // namespace Catch + +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + +#if defined( __GNUC__ ) +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_CLARA_HPP_INCLUDED + +namespace Catch { + + struct ConfigData; + + Clara::Parser makeCommandLineParser( ConfigData& config ); + +} // end namespace Catch + +#endif // CATCH_COMMANDLINE_HPP_INCLUDED + +namespace Catch { + + class Session : Detail::NonCopyable { + public: + + Session(); + ~Session(); + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char const * const * argv ); + #if defined(CATCH_CONFIG_WCHAR) && defined(_WIN32) && defined(UNICODE) + int applyCommandLine( int argc, wchar_t const * const * argv ); + #endif + + void useConfigData( ConfigData const& configData ); + + template<typename CharT> + int run(int argc, CharT const * const argv[]) { + if (m_startupExceptions) + return 1; + int returnCode = applyCommandLine(argc, argv); + if (returnCode == 0) + returnCode = run(); + return returnCode; + } + + int run(); + + Clara::Parser const& cli() const; + void cli( Clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + Clara::Parser m_cli; + ConfigData m_configData; + Detail::unique_ptr<Config> m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +#endif // CATCH_SESSION_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_HPP_INCLUDED +#define CATCH_TAG_ALIAS_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo): + tag(_tag), + lineInfo(_lineInfo) + {} + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED +#define CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif // CATCH_TAG_ALIAS_AUTOREGISTRAR_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +// inside templates (so `TEMPLATE_TEST_CASE` and co). +// **THIS IS DIFFERENT FOR STANDARD TESTS, WHERE GCC 9 IS SUFFICIENT** +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ < 10 +#pragma GCC diagnostic ignored "-Wparentheses" +#endif + + + + +#ifndef CATCH_TEST_MACROS_HPP_INCLUDED +#define CATCH_TEST_MACROS_HPP_INCLUDED + + + +#ifndef CATCH_TEST_MACRO_IMPL_HPP_INCLUDED +#define CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + + +#ifndef CATCH_ASSERTION_HANDLER_HPP_INCLUDED +#define CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + + +#ifndef CATCH_DECOMPOSER_HPP_INCLUDED +#define CATCH_DECOMPOSER_HPP_INCLUDED + + + +#ifndef CATCH_COMPARE_TRAITS_HPP_INCLUDED +#define CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#include <type_traits> + +namespace Catch { + namespace Detail { + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic push + // GCC likes to complain about comparing bool with 0, in the decltype() + // that defines the comparable traits below. +# pragma GCC diagnostic ignored "-Wbool-compare" + // "ordered comparison of pointer with integer zero" same as above, + // but it does not have a separate warning flag to suppress +# pragma GCC diagnostic ignored "-Wextra" + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +#if defined( __clang__ ) +# pragma clang diagnostic push + // Did you know that comparing floats with `0` directly + // is super-duper dangerous in unevaluated context? +# pragma clang diagnostic ignored "-Wfloat-equal" +#endif + +#define CATCH_DEFINE_COMPARABLE_TRAIT( id, op ) \ + template <typename, typename, typename = void> \ + struct is_##id##_comparable : std::false_type {}; \ + template <typename T, typename U> \ + struct is_##id##_comparable< \ + T, \ + U, \ + void_t<decltype( std::declval<T>() op std::declval<U>() )>> \ + : std::true_type {}; \ + template <typename, typename = void> \ + struct is_##id##_0_comparable : std::false_type {}; \ + template <typename T> \ + struct is_##id##_0_comparable<T, \ + void_t<decltype( std::declval<T>() op 0 )>> \ + : std::true_type {}; + + // We need all 6 pre-spaceship comparison ops: <, <=, >, >=, ==, != + CATCH_DEFINE_COMPARABLE_TRAIT( lt, < ) + CATCH_DEFINE_COMPARABLE_TRAIT( le, <= ) + CATCH_DEFINE_COMPARABLE_TRAIT( gt, > ) + CATCH_DEFINE_COMPARABLE_TRAIT( ge, >= ) + CATCH_DEFINE_COMPARABLE_TRAIT( eq, == ) + CATCH_DEFINE_COMPARABLE_TRAIT( ne, != ) + +#undef CATCH_DEFINE_COMPARABLE_TRAIT + +#if defined( __GNUC__ ) && !defined( __clang__ ) +# pragma GCC diagnostic pop +#endif +#if defined( __clang__ ) +# pragma clang diagnostic pop +#endif + + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_COMPARE_TRAITS_HPP_INCLUDED + + +#ifndef CATCH_LOGICAL_TRAITS_HPP_INCLUDED +#define CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include <type_traits> + +namespace Catch { +namespace Detail { + +#if defined( __cpp_lib_logical_traits ) && __cpp_lib_logical_traits >= 201510 + + using std::conjunction; + using std::disjunction; + using std::negation; + +#else + + template <class...> struct conjunction : std::true_type {}; + template <class B1> struct conjunction<B1> : B1 {}; + template <class B1, class... Bn> + struct conjunction<B1, Bn...> + : std::conditional_t<bool( B1::value ), conjunction<Bn...>, B1> {}; + + template <class...> struct disjunction : std::false_type {}; + template <class B1> struct disjunction<B1> : B1 {}; + template <class B1, class... Bn> + struct disjunction<B1, Bn...> + : std::conditional_t<bool( B1::value ), B1, disjunction<Bn...>> {}; + + template <class B> + struct negation : std::integral_constant<bool, !bool(B::value)> {}; + +#endif + +} // namespace Detail +} // namespace Catch + +#endif // CATCH_LOGICAL_TRAITS_HPP_INCLUDED + +#include <type_traits> +#include <iosfwd> + +/** \file + * Why does decomposing look the way it does: + * + * Conceptually, decomposing is simple. We change `REQUIRE( a == b )` into + * `Decomposer{} <= a == b`, so that `Decomposer{} <= a` is evaluated first, + * and our custom operator is used for `a == b`, because `a` is transformed + * into `ExprLhs<T&>` and then into `BinaryExpr<T&, U&>`. + * + * In practice, decomposing ends up a mess, because we have to support + * various fun things. + * + * 1) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with pointer constructor and deleted + * other constructors. Example: `REQUIRE((a <=> b) == 0)` in libstdc++ + * + * 2) Types that are only comparable with literal 0, and they do this by + * comparing against a magic type with consteval integer constructor. + * Example: `REQUIRE((a <=> b) == 0)` in current MSVC STL. + * + * 3) Types that have no linkage, and so we cannot form a reference to + * them. Example: some implementations of traits. + * + * 4) Starting with C++20, when the compiler sees `a == b`, it also uses + * `b == a` when constructing the overload set. For us this means that + * when the compiler handles `ExprLhs<T> == b`, it also tries to resolve + * the overload set for `b == ExprLhs<T>`. + * + * To accomodate these use cases, decomposer ended up rather complex. + * + * 1) These types are handled by adding SFINAE overloads to our comparison + * operators, checking whether `T == U` are comparable with the given + * operator, and if not, whether T (or U) are comparable with literal 0. + * If yes, the overload compares T (or U) with 0 literal inline in the + * definition. + * + * Note that for extra correctness, we check that the other type is + * either an `int` (literal 0 is captured as `int` by templates), or + * a `long` (some platforms use 0L for `NULL` and we want to support + * that for pointer comparisons). + * + * 2) For these types, `is_foo_comparable<T, int>` is true, but letting + * them fall into the overload that actually does `T == int` causes + * compilation error. Handling them requires that the decomposition + * is `constexpr`, so that P2564R3 applies and the `consteval` from + * their accompanying magic type is propagated through the `constexpr` + * call stack. + * + * However this is not enough to handle these types automatically, + * because our default is to capture types by reference, to avoid + * runtime copies. While these references cannot become dangling, + * they outlive the constexpr context and thus the default capture + * path cannot be actually constexpr. + * + * The solution is to capture these types by value, by explicitly + * specializing `Catch::capture_by_value` for them. Catch2 provides + * specialization for `std::foo_ordering`s, but users can specialize + * the trait for their own types as well. + * + * 3) If a type has no linkage, we also cannot capture it by reference. + * The solution is once again to capture them by value. We handle + * the common cases by using `std::is_arithmetic` as the default + * for `Catch::capture_by_value`, but that is only a some-effort + * heuristic. But as with 2), users can specialize `capture_by_value` + * for their own types as needed. + * + * 4) To support C++20 and make the SFINAE on our decomposing operators + * work, the SFINAE has to happen in return type, rather than in + * a template type. This is due to our use of logical type traits + * (`conjunction`/`disjunction`/`negation`), that we use to workaround + * an issue in older (9-) versions of GCC. I still blame C++20 for + * this, because without the comparison order switching, the logical + * traits could still be used in template type. + * + * There are also other side concerns, e.g. supporting both `REQUIRE(a)` + * and `REQUIRE(a == b)`, or making `REQUIRE_THAT(a, IsEqual(b))` slot + * nicely into the same expression handling logic, but these are rather + * straightforward and add only a bit of complexity (e.g. common base + * class for decomposed expressions). + */ + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#pragma warning(disable:4800) // Forcing result to true or false +#endif + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + +#if defined(CATCH_CPP20_OR_GREATER) && __has_include(<compare>) +# include <compare> +# if defined( __cpp_lib_three_way_comparison ) && \ + __cpp_lib_three_way_comparison >= 201907L +# define CATCH_CONFIG_CPP20_COMPARE_OVERLOADS +# endif +#endif + +namespace Catch { + + namespace Detail { + // This was added in C++20, but we require only C++14 for now. + template <typename T> + using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>; + } + + // Note: There is nothing that stops us from extending this, + // e.g. to `std::is_scalar`, but the more encompassing + // traits are usually also more expensive. For now we + // keep this as it used to be and it can be changed later. + template <typename T> + struct capture_by_value + : std::integral_constant<bool, std::is_arithmetic<T>{}> {}; + +#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS ) + template <> + struct capture_by_value<std::strong_ordering> : std::true_type {}; + template <> + struct capture_by_value<std::weak_ordering> : std::true_type {}; + template <> + struct capture_by_value<std::partial_ordering> : std::true_type {}; +#endif + + template <typename T> + struct always_false : std::false_type {}; + + class ITransientExpression { + bool m_isBinaryExpression; + bool m_result; + + protected: + ~ITransientExpression() = default; + + public: + constexpr auto isBinaryExpression() const -> bool { return m_isBinaryExpression; } + constexpr auto getResult() const -> bool { return m_result; } + //! This function **has** to be overriden by the derived class. + virtual void streamReconstructedExpression( std::ostream& os ) const; + + constexpr ITransientExpression( bool isBinaryExpression, bool result ) + : m_isBinaryExpression( isBinaryExpression ), + m_result( result ) + {} + + constexpr ITransientExpression( ITransientExpression const& ) = default; + constexpr ITransientExpression& operator=( ITransientExpression const& ) = default; + + friend std::ostream& operator<<(std::ostream& out, ITransientExpression const& expr) { + expr.streamReconstructedExpression(out); + return out; + } + }; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); + + template<typename LhsT, typename RhsT> + class BinaryExpr : public ITransientExpression { + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } + + public: + constexpr BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : ITransientExpression{ true, comparisonResult }, + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + + template<typename T> + auto operator && ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator || ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator == ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator != ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator > ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator < ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator >= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename T> + auto operator <= ( T ) const -> BinaryExpr<LhsT, RhsT const&> const { + static_assert(always_false<T>::value, + "chained comparisons are not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + }; + + template<typename LhsT> + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; + + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } + + public: + explicit constexpr UnaryExpr( LhsT lhs ) + : ITransientExpression{ false, static_cast<bool>(lhs) }, + m_lhs( lhs ) + {} + }; + + + template<typename LhsT> + class ExprLhs { + LhsT m_lhs; + public: + explicit constexpr ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( id, op ) \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ + Detail::negation<capture_by_value< \ + Detail::RemoveCVRef_t<RhsT>>>>::value, \ + BinaryExpr<LhsT, RhsT const&>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ + capture_by_value<RhsT>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \ + Detail::is_eq_0_comparable<LhsT>, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction<std::is_same<RhsT, int>, \ + std::is_same<RhsT, long>>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \ + Detail::is_eq_0_comparable<RhsT>, \ + /* We allow long because we want `ptr op NULL` to be accepted */ \ + Detail::disjunction<std::is_same<LhsT, int>, \ + std::is_same<LhsT, long>>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( eq, == ) + CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR( ne, != ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_EQUALITY_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( id, op ) \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ + Detail::negation<capture_by_value< \ + Detail::RemoveCVRef_t<RhsT>>>>::value, \ + BinaryExpr<LhsT, RhsT const&>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ + capture_by_value<RhsT>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \ + Detail::is_##id##_0_comparable<LhsT>, \ + std::is_same<RhsT, int>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + if ( rhs != 0 ) { throw_test_failure_exception(); } \ + return { \ + static_cast<bool>( lhs.m_lhs op 0 ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t< \ + Detail::conjunction< \ + Detail::negation<Detail::is_##id##_comparable<LhsT, RhsT>>, \ + Detail::is_##id##_0_comparable<RhsT>, \ + std::is_same<LhsT, int>>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + if ( lhs.m_lhs != 0 ) { throw_test_failure_exception(); } \ + return { static_cast<bool>( 0 op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( lt, < ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( le, <= ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( gt, > ) + CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR( ge, >= ) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_COMPARISON_OPERATOR + + +#define CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR( op ) \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ + -> std::enable_if_t< \ + !capture_by_value<Detail::RemoveCVRef_t<RhsT>>::value, \ + BinaryExpr<LhsT, RhsT const&>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } \ + template <typename RhsT> \ + constexpr friend auto operator op( ExprLhs&& lhs, RhsT rhs ) \ + -> std::enable_if_t<capture_by_value<RhsT>::value, \ + BinaryExpr<LhsT, RhsT>> { \ + return { \ + static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ + } + + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(|) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(&) + CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR(^) + + #undef CATCH_INTERNAL_DEFINE_EXPRESSION_OPERATOR + + template<typename RhsT> + friend auto operator && ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> { + static_assert(always_false<RhsT>::value, + "operator&& is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + template<typename RhsT> + friend auto operator || ( ExprLhs &&, RhsT && ) -> BinaryExpr<LhsT, RhsT const&> { + static_assert(always_false<RhsT>::value, + "operator|| is not supported inside assertions, " + "wrap the expression inside parentheses, or decompose it"); + } + + constexpr auto makeUnaryExpr() const -> UnaryExpr<LhsT> { + return UnaryExpr<LhsT>{ m_lhs }; + } + }; + + struct Decomposer { + template <typename T, + std::enable_if_t<!capture_by_value<Detail::RemoveCVRef_t<T>>::value, + int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> { + return ExprLhs<const T&>{ lhs }; + } + + template <typename T, + std::enable_if_t<capture_by_value<T>::value, int> = 0> + constexpr friend auto operator <= ( Decomposer &&, T value ) -> ExprLhs<T> { + return ExprLhs<T>{ value }; + } + }; + +} // end namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // CATCH_DECOMPOSER_HPP_INCLUDED + +#include <string> + +namespace Catch { + + struct AssertionReaction { + bool shouldDebugBreak = false; + bool shouldThrow = false; + bool shouldSkip = false; + }; + + class AssertionHandler { + AssertionInfo m_assertionInfo; + AssertionReaction m_reaction; + bool m_completed = false; + IResultCapture& m_resultCapture; + + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler() { + if ( !m_completed ) { + m_resultCapture.handleIncomplete( m_assertionInfo ); + } + } + + + template<typename T> + constexpr void handleExpr( ExprLhs<T> const& expr ) { + handleExpr( expr.makeUnaryExpr() ); + } + void handleExpr( ITransientExpression const& expr ); + + void handleMessage(ResultWas::OfType resultType, std::string&& message); + + void handleExceptionThrownAsExpected(); + void handleUnexpectedExceptionNotThrown(); + void handleExceptionNotThrownAsExpected(); + void handleThrowingCallSkipped(); + void handleUnexpectedInflightException(); + + void complete(); + + // query + auto allowThrows() const -> bool; + }; + + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str ); + +} // namespace Catch + +#endif // CATCH_ASSERTION_HANDLER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED +#define CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + + +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__##_catch_sr +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION"_catch_sr +#endif + +#endif // CATCH_PREPROCESSOR_INTERNAL_STRINGIFY_HPP_INCLUDED + +// We need this suppression to leak, because it took until GCC 10 +// for the front end to handle local suppression via _Pragma properly +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && __GNUC__ <= 9 + #pragma GCC diagnostic ignored "-Wparentheses" +#endif + +#if !defined(CATCH_CONFIG_DISABLE) + +#if defined(CATCH_CONFIG_FAST_COMPILE) || defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +#define INTERNAL_CATCH_TRY +#define INTERNAL_CATCH_CATCH( capturer ) + +#else // CATCH_CONFIG_FAST_COMPILE + +#define INTERNAL_CATCH_TRY try +#define INTERNAL_CATCH_CATCH( handler ) catch(...) { (handler).handleUnexpectedInflightException(); } + +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ + do { /* NOLINT(bugprone-infinite-loop) */ \ + /* The expression should not be evaluated, but warnings should hopefully be checked */ \ + CATCH_INTERNAL_IGNORE_BUT_WARN(__VA_ARGS__); \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handleExpr( Catch::Decomposer() <= __VA_ARGS__ ); /* NOLINT(bugprone-chained-comparison) */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + catchAssertionHandler.complete(); \ + } while( (void)0, (false) && static_cast<const bool&>( !!(__VA_ARGS__) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleExceptionNotThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + catchAssertionHandler.complete(); \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while( false ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(expr); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handleExceptionThrownAsExpected(); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while( false ) + + + +/////////////////////////////////////////////////////////////////////////////// +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_RESULT \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(__VA_ARGS__); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( ... ) { \ + Catch::handleExceptionMatchExpr( catchAssertionHandler, matcher ); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while( false ) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_TEST_MACRO_IMPL_HPP_INCLUDED + + +#ifndef CATCH_SECTION_HPP_INCLUDED +#define CATCH_SECTION_HPP_INCLUDED + + + + +/** \file + * Wrapper for the STATIC_ANALYSIS_SUPPORT configuration option + * + * Some of Catch2's macros can be defined differently to work better with + * static analysis tools, like clang-tidy or coverity. + * Currently the main use case is to show that `SECTION`s are executed + * exclusively, and not all in one run of a `TEST_CASE`. + */ + +#ifndef CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED +#define CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#if defined(__clang_analyzer__) || defined(__COVERITY__) + #define CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT +#endif + +#if defined( CATCH_INTERNAL_CONFIG_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_NO_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) && \ + !defined( CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT ) +# define CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT +#endif + + +#endif // CATCH_CONFIG_STATIC_ANALYSIS_SUPPORT_HPP_INCLUDED + + +#ifndef CATCH_TIMER_HPP_INCLUDED +#define CATCH_TIMER_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + class Timer { + uint64_t m_nanoseconds = 0; + public: + void start(); + auto getElapsedNanoseconds() const -> uint64_t; + auto getElapsedMicroseconds() const -> uint64_t; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; + }; + +} // namespace Catch + +#endif // CATCH_TIMER_HPP_INCLUDED + +namespace Catch { + + class Section : Detail::NonCopyable { + public: + Section( SectionInfo&& info ); + Section( SourceLineInfo const& _lineInfo, + StringRef _name, + const char* const = nullptr ); + ~Section(); + + // This indicates whether the section should be executed or not + explicit operator bool() const; + + private: + SectionInfo m_info; + + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::Section( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + if ( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_Section ) = \ + Catch::SectionInfo( \ + CATCH_INTERNAL_LINEINFO, \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ) \ + .str() ) ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else + +// These section definitions imply that at most one section at one level +// will be intered (because only one section's __LINE__ can be equal to +// the dummy `catchInternalSectionHint` variable from `TEST_CASE`). + +namespace Catch { + namespace Detail { + // Intentionally without linkage, as it should only be used as a dummy + // symbol for static analysis. + // The arguments are used as a dummy for checking warnings in the passed + // expressions. + int GetNewSectionHint( StringRef, const char* const = nullptr ); + } // namespace Detail +} // namespace Catch + + +# define INTERNAL_CATCH_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = \ + Catch::Detail::GetNewSectionHint(__VA_ARGS__); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define INTERNAL_CATCH_DYNAMIC_SECTION( ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + if ( [[maybe_unused]] const int catchInternalPreviousSectionHint = \ + catchInternalSectionHint, \ + catchInternalSectionHint = Catch::Detail::GetNewSectionHint( \ + ( Catch::ReusableStringStream() << __VA_ARGS__ ).str()); \ + catchInternalPreviousSectionHint == __LINE__ ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#endif + + +#endif // CATCH_SECTION_HPP_INCLUDED + + +#ifndef CATCH_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED +#define CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + +namespace Catch { + + class ITestInvoker { + public: + virtual void prepareTestCase(); + virtual void tearDownTestCase(); + virtual void invoke() const = 0; + virtual ~ITestInvoker(); // = default + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_TEST_INVOKER_HPP_INCLUDED + + +#ifndef CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED +#define CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +#define INTERNAL_CATCH_EXPAND1( param ) INTERNAL_CATCH_EXPAND2( param ) +#define INTERNAL_CATCH_EXPAND2( ... ) INTERNAL_CATCH_NO##__VA_ARGS__ +#define INTERNAL_CATCH_DEF( ... ) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF + +#define INTERNAL_CATCH_REMOVE_PARENS( ... ) \ + INTERNAL_CATCH_EXPAND1( INTERNAL_CATCH_DEF __VA_ARGS__ ) + +#endif // CATCH_PREPROCESSOR_REMOVE_PARENS_HPP_INCLUDED + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + + + +namespace Catch { + +template<typename C> +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); +public: + constexpr TestInvokerAsMethod( void ( C::*testAsMethod )() ) noexcept: + m_testAsMethod( testAsMethod ) {} + + void invoke() const override { + C obj; + (obj.*m_testAsMethod)(); + } +}; + +Detail::unique_ptr<ITestInvoker> makeTestInvoker( void(*testAsFunction)() ); + +template<typename C> +Detail::unique_ptr<ITestInvoker> makeTestInvoker( void (C::*testAsMethod)() ) { + return Detail::make_unique<TestInvokerAsMethod<C>>( testAsMethod ); +} + +template <typename C> +class TestInvokerFixture : public ITestInvoker { + void ( C::*m_testAsMethod )() const; + Detail::unique_ptr<C> m_fixture = nullptr; + +public: + constexpr TestInvokerFixture( void ( C::*testAsMethod )() const ) noexcept: + m_testAsMethod( testAsMethod ) {} + + void prepareTestCase() override { + m_fixture = Detail::make_unique<C>(); + } + + void tearDownTestCase() override { + m_fixture.reset(); + } + + void invoke() const override { + auto* f = m_fixture.get(); + ( f->*m_testAsMethod )(); + } +}; + +template<typename C> +Detail::unique_ptr<ITestInvoker> makeTestInvokerFixture( void ( C::*testAsMethod )() const ) { + return Detail::make_unique<TestInvokerFixture<C>>( testAsMethod ); +} + +struct NameAndTags { + constexpr NameAndTags( StringRef name_ = StringRef(), + StringRef tags_ = StringRef() ) noexcept: + name( name_ ), tags( tags_ ) {} + StringRef name; + StringRef tags; +}; + +struct AutoReg : Detail::NonCopyable { + AutoReg( Detail::unique_ptr<ITestInvoker> invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; +}; + +} // end namespace Catch + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static inline void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + } \ + void TestName::test() +#endif + + +#if !defined(CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__ ) + +#else // ^^ !CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT | vv CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + +// Dummy registrator for the dumy test case macros +namespace Catch { + namespace Detail { + struct DummyUse { + DummyUse( void ( * )( int ), Catch::NameAndTags const& ); + }; + } // namespace Detail +} // namespace Catch + +// Note that both the presence of the argument and its exact name are +// necessary for the section support. + +// We provide a shadowed variable so that a `SECTION` inside non-`TEST_CASE` +// tests can compile. The redefined `TEST_CASE` shadows this with param. +static int catchInternalSectionHint = 0; + +# define INTERNAL_CATCH_TESTCASE2( fname, ... ) \ + static void fname( int ); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + static const Catch::Detail::DummyUse INTERNAL_CATCH_UNIQUE_NAME( \ + dummyUser )( &(fname), Catch::NameAndTags{ __VA_ARGS__ } ); \ + CATCH_INTERNAL_SUPPRESS_SHADOW_WARNINGS \ + static void fname( [[maybe_unused]] int catchInternalSectionHint ) \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +# define INTERNAL_CATCH_TESTCASE( ... ) \ + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( dummyFunction ), __VA_ARGS__ ) + + +#endif // CATCH_CONFIG_EXPERIMENTAL_STATIC_ANALYSIS_SUPPORT + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace{ \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName) { \ + void test(); \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &TestName::test ), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( TestName, ClassName, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS( ClassName ) { \ + void test() const; \ + }; \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvokerFixture( &TestName::test ), \ + CATCH_INTERNAL_LINEINFO, \ + #ClassName##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + void TestName::test() const + #define INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), ClassName, __VA_ARGS__ ) + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace { \ + const Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( \ + Catch::makeTestInvoker( &QualifiedMethod ), \ + CATCH_INTERNAL_LINEINFO, \ + "&" #QualifiedMethod##_catch_sr, \ + Catch::NameAndTags{ __VA_ARGS__ } ); \ + } /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + do { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + } while(false) + + +#endif // CATCH_TEST_REGISTRY_HPP_INCLUDED + + +// All of our user-facing macros support configuration toggle, that +// forces them to be defined prefixed with CATCH_. We also like to +// support another toggle that can minimize (disable) their implementation. +// Given this, we have 4 different configuration options below + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CATCH_SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define CATCH_STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) static_assert( __VA_ARGS__ , #__VA_ARGS__ ); CATCH_SUCCEED( #__VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); CATCH_SUCCEED( #__VA_ARGS__ ) + #else + #define CATCH_STATIC_REQUIRE( ... ) CATCH_REQUIRE( __VA_ARGS__ ) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) CATCH_REQUIRE_FALSE( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK( ... ) CATCH_CHECK( __VA_ARGS__ ) + #define CATCH_STATIC_CHECK_FALSE( ... ) CATCH_CHECK_FALSE( __VA_ARGS__ ) + #endif + + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define CATCH_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define CATCH_AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define CATCH_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define CATCH_AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define CATCH_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define CATCH_AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, implemented | vv prefixed, disabled + + #define CATCH_REQUIRE( ... ) (void)(0) + #define CATCH_REQUIRE_FALSE( ... ) (void)(0) + + #define CATCH_REQUIRE_THROWS( ... ) (void)(0) + #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + + #define CATCH_CHECK( ... ) (void)(0) + #define CATCH_CHECK_FALSE( ... ) (void)(0) + #define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) + #define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CATCH_CHECK_NOFAIL( ... ) (void)(0) + + #define CATCH_CHECK_THROWS( ... ) (void)(0) + #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CATCH_CHECK_NOTHROW( ... ) (void)(0) + + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) + #define CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define CATCH_SECTION( ... ) + #define CATCH_DYNAMIC_SECTION( ... ) + #define CATCH_FAIL( ... ) (void)(0) + #define CATCH_FAIL_CHECK( ... ) (void)(0) + #define CATCH_SUCCEED( ... ) (void)(0) + #define CATCH_SKIP( ... ) (void)(0) + + #define CATCH_STATIC_REQUIRE( ... ) (void)(0) + #define CATCH_STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define CATCH_STATIC_CHECK( ... ) (void)(0) + #define CATCH_STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + #define CATCH_GIVEN( desc ) + #define CATCH_AND_GIVEN( desc ) + #define CATCH_WHEN( desc ) + #define CATCH_AND_WHEN( desc ) + #define CATCH_THEN( desc ) + #define CATCH_AND_THEN( desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) // ^^ prefixed, disabled | vv unprefixed, implemented + + #define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + + #define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) + #define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + #define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + #define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + #define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + + #define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) + #define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TEST_CASE_PERSISTENT_FIXTURE( className, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define DYNAMIC_SECTION( ... ) INTERNAL_CATCH_DYNAMIC_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + #define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + #define SKIP( ... ) INTERNAL_CATCH_MSG( "SKIP", Catch::ResultWas::ExplicitSkip, Catch::ResultDisposition::Normal, __VA_ARGS__ ) + + + #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE) + #define STATIC_REQUIRE( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #define STATIC_CHECK( ... ) static_assert( __VA_ARGS__, #__VA_ARGS__ ); SUCCEED( #__VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) static_assert( !(__VA_ARGS__), "!(" #__VA_ARGS__ ")" ); SUCCEED( "!(" #__VA_ARGS__ ")" ) + #else + #define STATIC_REQUIRE( ... ) REQUIRE( __VA_ARGS__ ) + #define STATIC_REQUIRE_FALSE( ... ) REQUIRE_FALSE( __VA_ARGS__ ) + #define STATIC_CHECK( ... ) CHECK( __VA_ARGS__ ) + #define STATIC_CHECK_FALSE( ... ) CHECK_FALSE( __VA_ARGS__ ) + #endif + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + #define GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Given: " << desc ) + #define AND_GIVEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( "And given: " << desc ) + #define WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " When: " << desc ) + #define AND_WHEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And when: " << desc ) + #define THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " Then: " << desc ) + #define AND_THEN( desc ) INTERNAL_CATCH_DYNAMIC_SECTION( " And: " << desc ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) // ^^ unprefixed, implemented | vv unprefixed, disabled + + #define REQUIRE( ... ) (void)(0) + #define REQUIRE_FALSE( ... ) (void)(0) + + #define REQUIRE_THROWS( ... ) (void)(0) + #define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) + #define REQUIRE_NOTHROW( ... ) (void)(0) + + #define CHECK( ... ) (void)(0) + #define CHECK_FALSE( ... ) (void)(0) + #define CHECKED_IF( ... ) if (__VA_ARGS__) + #define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) + #define CHECK_NOFAIL( ... ) (void)(0) + + #define CHECK_THROWS( ... ) (void)(0) + #define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) + #define CHECK_NOTHROW( ... ) (void)(0) + + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ )) + #define METHOD_AS_TEST_CASE( method, ... ) + #define TEST_CASE_PERSISTENT_FIXTURE( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), __VA_ARGS__) + #define REGISTER_TEST_CASE( Function, ... ) (void)(0) + #define SECTION( ... ) + #define DYNAMIC_SECTION( ... ) + #define FAIL( ... ) (void)(0) + #define FAIL_CHECK( ... ) (void)(0) + #define SUCCEED( ... ) (void)(0) + #define SKIP( ... ) (void)(0) + + #define STATIC_REQUIRE( ... ) (void)(0) + #define STATIC_REQUIRE_FALSE( ... ) (void)(0) + #define STATIC_CHECK( ... ) (void)(0) + #define STATIC_CHECK_FALSE( ... ) (void)(0) + + // "BDD-style" convenience wrappers + #define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ) ) + #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEST_ ), className ) + + #define GIVEN( desc ) + #define AND_GIVEN( desc ) + #define WHEN( desc ) + #define AND_WHEN( desc ) + #define THEN( desc ) + #define AND_THEN( desc ) + +#endif // ^^ unprefixed, disabled + +// end of user facing macros + +#endif // CATCH_TEST_MACROS_HPP_INCLUDED + + +#ifndef CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED +#define CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_PREPROCESSOR_HPP_INCLUDED +#define CATCH_PREPROCESSOR_HPP_INCLUDED + + +#if defined(__GNUC__) +// We need to silence "empty __VA_ARGS__ warning", and using just _Pragma does not work +#pragma GCC system_header +#endif + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS_GEN(__VA_ARGS__)>())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template<typename...> struct TypeList {};\ + template<typename...Ts>\ + constexpr auto get_wrapper() noexcept -> TypeList<Ts...> { return {}; }\ + template<template<typename...> class...> struct TemplateTypeList{};\ + template<template<typename...> class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList<Cs...> { return {}; }\ + template<typename...>\ + struct append;\ + template<typename...>\ + struct rewrap;\ + template<template<typename...> class, typename...>\ + struct create;\ + template<template<typename...> class, typename>\ + struct convert;\ + \ + template<typename T> \ + struct append<T> { using type = T; };\ + template< template<typename...> class L1, typename...E1, template<typename...> class L2, typename...E2, typename...Rest>\ + struct append<L1<E1...>, L2<E2...>, Rest...> { using type = typename append<L1<E1...,E2...>, Rest...>::type; };\ + template< template<typename...> class L1, typename...E1, typename...Rest>\ + struct append<L1<E1...>, TypeList<mpl_::na>, Rest...> { using type = L1<E1...>; };\ + \ + template< template<typename...> class Container, template<typename...> class List, typename...elems>\ + struct rewrap<TemplateTypeList<Container>, List<elems...>> { using type = TypeList<Container<elems...>>; };\ + template< template<typename...> class Container, template<typename...> class List, class...Elems, typename...Elements>\ + struct rewrap<TemplateTypeList<Container>, List<Elems...>, Elements...> { using type = typename append<TypeList<Container<Elems...>>, typename rewrap<TemplateTypeList<Container>, Elements...>::type>::type; };\ + \ + template<template <typename...> class Final, template< typename...> class...Containers, typename...Types>\ + struct create<Final, TemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<TemplateTypeList<Containers>, Types...>::type...>::type; };\ + template<template <typename...> class Final, template <typename...> class List, typename...Ts>\ + struct convert<Final, List<Ts...>> { using type = typename append<Final<>,TypeList<Ts>...>::type; }; + +#define INTERNAL_CATCH_NTTP_1(signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> struct Nttp{};\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + constexpr auto get_wrapper() noexcept -> Nttp<__VA_ARGS__> { return {}; } \ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...> struct NttpTemplateTypeList{};\ + template<template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Cs>\ + constexpr auto get_wrapper() noexcept -> NttpTemplateTypeList<Cs...> { return {}; } \ + \ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>> { using type = TypeList<Container<__VA_ARGS__>>; };\ + template< template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class Container, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class List, INTERNAL_CATCH_REMOVE_PARENS(signature), typename...Elements>\ + struct rewrap<NttpTemplateTypeList<Container>, List<__VA_ARGS__>, Elements...> { using type = typename append<TypeList<Container<__VA_ARGS__>>, typename rewrap<NttpTemplateTypeList<Container>, Elements...>::type>::type; };\ + template<template <typename...> class Final, template<INTERNAL_CATCH_REMOVE_PARENS(signature)> class...Containers, typename...Types>\ + struct create<Final, NttpTemplateTypeList<Containers...>, TypeList<Types...>> { using type = typename append<Final<>, typename rewrap<NttpTemplateTypeList<Containers>, Types...>::type...>::type; }; + +#define INTERNAL_CATCH_DECLARE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DECLARE_SIG_TEST_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_DEFINE_SIG_TEST0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST1(TestName, signature)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_X(TestName, signature,...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + static void TestName() + +#define INTERNAL_CATCH_NTTP_REGISTER0(TestFunc, signature)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<Type>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER(TestFunc, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestFunc<__VA_ARGS__>), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD0(TestName, signature, ...)\ + template<typename Type>\ + void reg_test(TypeList<Type>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<Type>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_NTTP_REGISTER_METHOD(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)>\ + void reg_test(Nttp<__VA_ARGS__>, Catch::StringRef className, Catch::NameAndTags nameAndTags)\ + {\ + Catch::AutoReg( Catch::makeTestInvoker(&TestName<__VA_ARGS__>::test), CATCH_INTERNAL_LINEINFO, className, nameAndTags);\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0(TestName, ClassName) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1(TestName, ClassName, signature)\ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<TestType> { \ + void test();\ + } + +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X(TestName, ClassName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName)<__VA_ARGS__> { \ + void test();\ + } + +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0(TestName) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1(TestName, signature)\ + template<typename TestType> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<TestType>::test() +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X(TestName, signature, ...)\ + template<INTERNAL_CATCH_REMOVE_PARENS(signature)> \ + void INTERNAL_CATCH_MAKE_NAMESPACE(TestName)::TestName<__VA_ARGS__>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_NTTP_0 +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1(__VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_1( __VA_ARGS__),INTERNAL_CATCH_NTTP_1( __VA_ARGS__), INTERNAL_CATCH_NTTP_0) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__) +#else +#define INTERNAL_CATCH_NTTP_0(signature) +#define INTERNAL_CATCH_NTTP_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_1,INTERNAL_CATCH_NTTP_1, INTERNAL_CATCH_NTTP_0)( __VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD1, INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X,INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD_X, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD1, INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD0)(TestName, ClassName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD, INTERNAL_CATCH_NTTP_REGISTER_METHOD0, INTERNAL_CATCH_NTTP_REGISTER_METHOD0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_NTTP_REG_GEN(TestFunc, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER, INTERNAL_CATCH_NTTP_REGISTER0, INTERNAL_CATCH_NTTP_REGISTER0)(TestFunc, __VA_ARGS__)) +#define INTERNAL_CATCH_DEFINE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DEFINE_SIG_TEST1, INTERNAL_CATCH_DEFINE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_DECLARE_SIG_TEST(TestName, ...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL( "dummy", __VA_ARGS__, INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DEFINE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X,INTERNAL_CATCH_DECLARE_SIG_TEST_X, INTERNAL_CATCH_DECLARE_SIG_TEST1, INTERNAL_CATCH_DECLARE_SIG_TEST0)(TestName, __VA_ARGS__)) +#define INTERNAL_CATCH_REMOVE_PARENS_GEN(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_VA_NARGS_IMPL(__VA_ARGS__, INTERNAL_CATCH_REMOVE_PARENS_11_ARG,INTERNAL_CATCH_REMOVE_PARENS_10_ARG,INTERNAL_CATCH_REMOVE_PARENS_9_ARG,INTERNAL_CATCH_REMOVE_PARENS_8_ARG,INTERNAL_CATCH_REMOVE_PARENS_7_ARG,INTERNAL_CATCH_REMOVE_PARENS_6_ARG,INTERNAL_CATCH_REMOVE_PARENS_5_ARG,INTERNAL_CATCH_REMOVE_PARENS_4_ARG,INTERNAL_CATCH_REMOVE_PARENS_3_ARG,INTERNAL_CATCH_REMOVE_PARENS_2_ARG,INTERNAL_CATCH_REMOVE_PARENS_1_ARG)(__VA_ARGS__)) +#endif + +#endif // CATCH_PREPROCESSOR_HPP_INCLUDED + + +// GCC 5 and older do not properly handle disabling unused-variable warning +// with a _Pragma. This means that we have to leak the suppression to the +// user code as well :-( +#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ <= 5 +#pragma GCC diagnostic ignored "-Wunused-variable" +#endif + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( TestName, TestFunc, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ + namespace{ \ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + } \ + } \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) + #endif + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) + #else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) + #endif +#endif + + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_2(TestName, TestFunc, Name, Tags, Signature, ... )\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + INTERNAL_CATCH_DECLARE_SIG_TEST(TestFunc, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_NTTP_REG_GEN(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types> \ + struct TestName{\ + TestName(){\ + size_t index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)}; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */\ + using expander = size_t[]; /* NOLINT(cppcoreguidelines-avoid-c-arrays,modernize-avoid-c-arrays,hicpp-avoid-c-arrays) */\ + (void)expander{(reg_test(Types{}, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static const int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestName<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST(TestFunc,INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE(Name, Tags, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename TestType, __VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG(Name, Tags, Signature, ...) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(TestName, TestFuncName, Name, Tags, Signature, TmplTypes, TypesList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template<typename TestType> static void TestFuncName(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName) { \ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature)) \ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + size_t index = 0; \ + using expander = size_t[]; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFuncName<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */\ + } \ + }; \ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename create<TestName, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + } \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFuncName() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T,__VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE(Name, Tags, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, typename T, __VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2(INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG(Name, Tags, Signature, ...)\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2(TestName, TestFunc, Name, Tags, TmplList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template<typename TestType> static void TestFunc(); \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){\ + INTERNAL_CATCH_TYPE_GEN\ + template<typename... Types> \ + struct TestName { \ + void reg_tests() { \ + size_t index = 0; \ + using expander = size_t[]; \ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestFunc<Types> ), CATCH_INTERNAL_LINEINFO, Catch::StringRef(), Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */\ + } \ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){ \ + using TestInit = typename convert<TestName, TmplList>::type; \ + TestInit t; \ + t.reg_tests(); \ + return 0; \ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + static void TestFunc() + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), Name, Tags, TmplList ) + + + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, Signature, ... ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + INTERNAL_CATCH_DECLARE_SIG_TEST_METHOD(TestName, ClassName, INTERNAL_CATCH_REMOVE_PARENS(Signature));\ + INTERNAL_CATCH_NTTP_REG_METHOD_GEN(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types> \ + struct TestNameClass{\ + TestNameClass(){\ + size_t index = 0; \ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, __VA_ARGS__)};\ + using expander = size_t[];\ + (void)expander{(reg_test(Types{}, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index]), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + TestNameClass<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(__VA_ARGS__)>();\ + return 0;\ + }();\ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + INTERNAL_CATCH_DEFINE_SIG_TEST_METHOD(TestName, INTERNAL_CATCH_REMOVE_PARENS(Signature)) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( ClassName, Name, Tags,... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, typename T, __VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... ) \ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_CLASS_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ) , ClassName, Name, Tags, Signature, __VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2(TestNameClass, TestName, ClassName, Name, Tags, Signature, TmplTypes, TypesList)\ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestNameClass) {\ + INTERNAL_CATCH_TYPE_GEN \ + INTERNAL_CATCH_NTTP_GEN(INTERNAL_CATCH_REMOVE_PARENS(Signature))\ + template<typename...Types>\ + struct TestNameClass{\ + void reg_tests(){\ + std::size_t index = 0;\ + using expander = std::size_t[];\ + constexpr char const* tmpl_types[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TmplTypes))};\ + constexpr char const* types_list[] = {CATCH_REC_LIST(INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS, INTERNAL_CATCH_REMOVE_PARENS(TypesList))};\ + constexpr auto num_types = sizeof(types_list) / sizeof(types_list[0]);\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ Name " - " + std::string(tmpl_types[index / num_types]) + '<' + std::string(types_list[index % num_types]) + '>', Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename create<TestNameClass, decltype(get_wrapper<INTERNAL_CATCH_REMOVE_PARENS(TmplTypes)>()), TypeList<INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(INTERNAL_CATCH_REMOVE_PARENS(TypesList))>>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }\ + }\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + void TestName<TestType>::test() + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( ClassName, Name, Tags, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, typename T,__VA_ARGS__ ) ) +#endif + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature, __VA_ARGS__ ) +#else + #define INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( ClassName, Name, Tags, Signature, ... )\ + INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, Signature,__VA_ARGS__ ) ) +#endif + + #define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( TestNameClass, TestName, ClassName, Name, Tags, TmplList) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_UNUSED_VARIABLE_WARNINGS \ + CATCH_INTERNAL_SUPPRESS_COMMA_WARNINGS \ + template<typename TestType> \ + struct TestName : INTERNAL_CATCH_REMOVE_PARENS(ClassName <TestType>) { \ + void test();\ + };\ + namespace {\ + namespace INTERNAL_CATCH_MAKE_NAMESPACE(TestName){ \ + INTERNAL_CATCH_TYPE_GEN\ + template<typename...Types>\ + struct TestNameClass{\ + void reg_tests(){\ + size_t index = 0;\ + using expander = size_t[];\ + (void)expander{(Catch::AutoReg( Catch::makeTestInvoker( &TestName<Types>::test ), CATCH_INTERNAL_LINEINFO, #ClassName##_catch_sr, Catch::NameAndTags{ Name " - " INTERNAL_CATCH_STRINGIZE(TmplList) " - " + std::to_string(index), Tags } ), index++)... };/* NOLINT */ \ + }\ + };\ + static int INTERNAL_CATCH_UNIQUE_NAME( globalRegistrar ) = [](){\ + using TestInit = typename convert<TestNameClass, TmplList>::type;\ + TestInit t;\ + t.reg_tests();\ + return 0;\ + }(); \ + }}\ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + template<typename TestType> \ + void TestName<TestType>::test() + +#define INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD(ClassName, Name, Tags, TmplList) \ + INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD_2( INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), INTERNAL_CATCH_UNIQUE_NAME( CATCH2_INTERNAL_TEMPLATE_TEST_ ), ClassName, Name, Tags, TmplList ) + + +#endif // CATCH_TEMPLATE_TEST_REGISTRY_HPP_INCLUDED + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) + #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #else + #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #endif + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) + #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) + #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) + #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) + #else + #define CATCH_TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) + #define CATCH_TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) + #define CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) + #endif + + // When disabled, these can be shared between proper preprocessor and MSVC preprocessor + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_TEMPLATE_LIST_TEST_CASE( ... ) CATCH_TEMPLATE_TEST_CASE(__VA_ARGS__) + #define CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) + #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) + #define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE(__VA_ARGS__) + #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #else + #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE( __VA_ARGS__ ) ) + #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG( __VA_ARGS__ ) ) + #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) + #define TEMPLATE_PRODUCT_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE( __VA_ARGS__ ) ) + #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_SIG( __VA_ARGS__ ) ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, __VA_ARGS__ ) ) + #define TEMPLATE_LIST_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE( __VA_ARGS__ ) ) + #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_LIST_TEST_CASE_METHOD( className, __VA_ARGS__ ) ) + #endif + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR + #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) + #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) + #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__) + #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) + #else + #define TEMPLATE_TEST_CASE( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_NO_REGISTRATION(__VA_ARGS__) ) + #define TEMPLATE_TEST_CASE_SIG( ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_SIG_NO_REGISTRATION(__VA_ARGS__) ) + #define TEMPLATE_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_NO_REGISTRATION(className, __VA_ARGS__ ) ) + #define TEMPLATE_TEST_CASE_METHOD_SIG( className, ... ) INTERNAL_CATCH_EXPAND_VARGS( INTERNAL_CATCH_TEMPLATE_TEST_CASE_METHOD_SIG_NO_REGISTRATION(className, __VA_ARGS__ ) ) + #endif + + // When disabled, these can be shared between proper preprocessor and MSVC preprocessor + #define TEMPLATE_PRODUCT_TEST_CASE( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_SIG( ... ) TEMPLATE_TEST_CASE( __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define TEMPLATE_PRODUCT_TEST_CASE_METHOD_SIG( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define TEMPLATE_LIST_TEST_CASE( ... ) TEMPLATE_TEST_CASE(__VA_ARGS__) + #define TEMPLATE_LIST_TEST_CASE_METHOD( className, ... ) TEMPLATE_TEST_CASE_METHOD( className, __VA_ARGS__ ) + +#endif // end of user facing macro declarations + + +#endif // CATCH_TEMPLATE_TEST_MACROS_HPP_INCLUDED + + +#ifndef CATCH_TEST_CASE_INFO_HPP_INCLUDED +#define CATCH_TEST_CASE_INFO_HPP_INCLUDED + + + +#include <cstdint> +#include <string> +#include <vector> + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + /** + * A **view** of a tag string that provides case insensitive comparisons + * + * Note that in Catch2 internals, the square brackets around tags are + * not a part of tag's representation, so e.g. "[cool-tag]" is represented + * as "cool-tag" internally. + */ + struct Tag { + constexpr Tag(StringRef original_): + original(original_) + {} + StringRef original; + + friend bool operator< ( Tag const& lhs, Tag const& rhs ); + friend bool operator==( Tag const& lhs, Tag const& rhs ); + }; + + class ITestInvoker; + struct NameAndTags; + + enum class TestCaseProperties : uint8_t { + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 + }; + + /** + * Various metadata about the test case. + * + * A test case is uniquely identified by its (class)name and tags + * combination, with source location being ignored, and other properties + * being determined from tags. + * + * Tags are kept sorted. + */ + struct TestCaseInfo : Detail::NonCopyable { + + TestCaseInfo(StringRef _className, + NameAndTags const& _nameAndTags, + SourceLineInfo const& _lineInfo); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + // Adds the tag(s) with test's filename (for the -# flag) + void addFilenameTag(); + + //! Orders by name, classname and tags + friend bool operator<( TestCaseInfo const& lhs, + TestCaseInfo const& rhs ); + + + std::string tagsAsString() const; + + std::string name; + StringRef className; + private: + std::string backingTags; + // Internally we copy tags to the backing storage and then add + // refs to this storage to the tags vector. + void internalAppendTag(StringRef tagString); + public: + std::vector<Tag> tags; + SourceLineInfo lineInfo; + TestCaseProperties properties = TestCaseProperties::None; + }; + + /** + * Wrapper over the test case information and the test case invoker + * + * Does not own either, and is specifically made to be cheap + * to copy around. + */ + class TestCaseHandle { + TestCaseInfo* m_info; + ITestInvoker* m_invoker; + public: + constexpr TestCaseHandle(TestCaseInfo* info, ITestInvoker* invoker) : + m_info(info), m_invoker(invoker) {} + + void prepareTestCase() const { + m_invoker->prepareTestCase(); + } + + void tearDownTestCase() const { + m_invoker->tearDownTestCase(); + } + + void invoke() const { + m_invoker->invoke(); + } + + constexpr TestCaseInfo const& getTestCaseInfo() const { + return *m_info; + } + }; + + Detail::unique_ptr<TestCaseInfo> + makeTestCaseInfo( StringRef className, + NameAndTags const& nameAndTags, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_CASE_INFO_HPP_INCLUDED + + +#ifndef CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED +#define CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED +#define CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED + + +#include <string> +#include <vector> + +namespace Catch { + using exceptionTranslateFunction = std::string(*)(); + + class IExceptionTranslator; + using ExceptionTranslators = std::vector<Detail::unique_ptr<IExceptionTranslator const>>; + + class IExceptionTranslator { + public: + virtual ~IExceptionTranslator(); // = default + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; + }; + + class IExceptionTranslatorRegistry { + public: + virtual ~IExceptionTranslatorRegistry(); // = default + virtual std::string translateActiveException() const = 0; + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_EXCEPTION_HPP_INCLUDED + +#include <exception> + +namespace Catch { + namespace Detail { + void registerTranslatorImpl( + Detail::unique_ptr<IExceptionTranslator>&& translator ); + } + + class ExceptionTranslatorRegistrar { + template<typename T> + class ExceptionTranslator : public IExceptionTranslator { + public: + + constexpr ExceptionTranslator( std::string(*translateFunction)( T const& ) ) + : m_translateFunction( translateFunction ) + {} + + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + try { + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); + } + catch( T const& ex ) { + return m_translateFunction( ex ); + } +#else + return "You should never get here!"; +#endif + } + + protected: + std::string(*m_translateFunction)( T const& ); + }; + + public: + template<typename T> + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T const& ) ) { + Detail::registerTranslatorImpl( + Detail::make_unique<ExceptionTranslator<T>>( + translateFunction ) ); + } + }; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + static std::string translatorName( signature ) + +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + + +// This macro is always prefixed +#if !defined(CATCH_CONFIG_DISABLE) +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) +#else +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) +#endif + + +#endif // CATCH_TRANSLATE_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_VERSION_HPP_INCLUDED +#define CATCH_VERSION_HPP_INCLUDED + +#include <iosfwd> + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +#endif // CATCH_VERSION_HPP_INCLUDED + + +#ifndef CATCH_VERSION_MACROS_HPP_INCLUDED +#define CATCH_VERSION_MACROS_HPP_INCLUDED + +#define CATCH_VERSION_MAJOR 3 +#define CATCH_VERSION_MINOR 8 +#define CATCH_VERSION_PATCH 1 + +#endif // CATCH_VERSION_MACROS_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2's Generator support. It includes + * **all** of Catch2 headers related to generators. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header is added to either the `generators` folder, + * or to the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_GENERATORS_ALL_HPP_INCLUDED +#define CATCH_GENERATORS_ALL_HPP_INCLUDED + + + +#ifndef CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED +#define CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED + +#include <exception> + +namespace Catch { + + // Exception type to be thrown when a Generator runs into an error, + // e.g. it cannot initialize the first return value based on + // runtime information + class GeneratorException : public std::exception { + const char* const m_msg = ""; + + public: + GeneratorException(const char* msg): + m_msg(msg) + {} + + const char* what() const noexcept override final; + }; + +} // end namespace Catch + +#endif // CATCH_GENERATOR_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_GENERATORS_HPP_INCLUDED +#define CATCH_GENERATORS_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED +#define CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + namespace Generators { + class GeneratorUntypedBase { + // Caches result from `toStringImpl`, assume that when it is an + // empty string, the cache is invalidated. + mutable std::string m_stringReprCache; + + // Counts based on `next` returning true + std::size_t m_currentElementIndex = 0; + + /** + * Attempts to move the generator to the next element + * + * Returns true iff the move succeeded (and a valid element + * can be retrieved). + */ + virtual bool next() = 0; + + //! Customization point for `currentElementAsString` + virtual std::string stringifyImpl() const = 0; + + public: + GeneratorUntypedBase() = default; + // Generation of copy ops is deprecated (and Clang will complain) + // if there is a user destructor defined + GeneratorUntypedBase(GeneratorUntypedBase const&) = default; + GeneratorUntypedBase& operator=(GeneratorUntypedBase const&) = default; + + virtual ~GeneratorUntypedBase(); // = default; + + /** + * Attempts to move the generator to the next element + * + * Serves as a non-virtual interface to `next`, so that the + * top level interface can provide sanity checking and shared + * features. + * + * As with `next`, returns true iff the move succeeded and + * the generator has new valid element to provide. + */ + bool countedNext(); + + std::size_t currentElementIndex() const { return m_currentElementIndex; } + + /** + * Returns generator's current element as user-friendly string. + * + * By default returns string equivalent to calling + * `Catch::Detail::stringify` on the current element, but generators + * can customize their implementation as needed. + * + * Not thread-safe due to internal caching. + * + * The returned ref is valid only until the generator instance + * is destructed, or it moves onto the next element, whichever + * comes first. + */ + StringRef currentElementAsString() const; + }; + using GeneratorBasePtr = Catch::Detail::unique_ptr<GeneratorUntypedBase>; + + } // namespace Generators + + class IGeneratorTracker { + public: + virtual ~IGeneratorTracker(); // = default; + virtual auto hasGenerator() const -> bool = 0; + virtual auto getGenerator() const -> Generators::GeneratorBasePtr const& = 0; + virtual void setGenerator( Generators::GeneratorBasePtr&& generator ) = 0; + }; + +} // namespace Catch + +#endif // CATCH_INTERFACES_GENERATORTRACKER_HPP_INCLUDED + +#include <vector> +#include <tuple> + +namespace Catch { + +namespace Generators { + +namespace Detail { + + //! Throws GeneratorException with the provided message + [[noreturn]] + void throw_generator_exception(char const * msg); + +} // end namespace detail + + template<typename T> + class IGenerator : public GeneratorUntypedBase { + std::string stringifyImpl() const override { + return ::Catch::Detail::stringify( get() ); + } + + public: + // Returns the current element of the generator + // + // \Precondition The generator is either freshly constructed, + // or the last call to `next()` returned true + virtual T const& get() const = 0; + using type = T; + }; + + template <typename T> + using GeneratorPtr = Catch::Detail::unique_ptr<IGenerator<T>>; + + template <typename T> + class GeneratorWrapper final { + GeneratorPtr<T> m_generator; + public: + //! Takes ownership of the passed pointer. + GeneratorWrapper(IGenerator<T>* generator): + m_generator(generator) {} + GeneratorWrapper(GeneratorPtr<T> generator): + m_generator(CATCH_MOVE(generator)) {} + + T const& get() const { + return m_generator->get(); + } + bool next() { + return m_generator->countedNext(); + } + }; + + + template<typename T> + class SingleValueGenerator final : public IGenerator<T> { + T m_value; + public: + SingleValueGenerator(T const& value) : + m_value(value) + {} + SingleValueGenerator(T&& value): + m_value(CATCH_MOVE(value)) + {} + + T const& get() const override { + return m_value; + } + bool next() override { + return false; + } + }; + + template<typename T> + class FixedValuesGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "FixedValuesGenerator does not support bools because of std::vector<bool>" + "specialization, use SingleValue Generator instead."); + std::vector<T> m_values; + size_t m_idx = 0; + public: + FixedValuesGenerator( std::initializer_list<T> values ) : m_values( values ) {} + + T const& get() const override { + return m_values[m_idx]; + } + bool next() override { + ++m_idx; + return m_idx < m_values.size(); + } + }; + + template <typename T, typename DecayedT = std::decay_t<T>> + GeneratorWrapper<DecayedT> value( T&& value ) { + return GeneratorWrapper<DecayedT>( + Catch::Detail::make_unique<SingleValueGenerator<DecayedT>>( + CATCH_FORWARD( value ) ) ); + } + template <typename T> + GeneratorWrapper<T> values(std::initializer_list<T> values) { + return GeneratorWrapper<T>(Catch::Detail::make_unique<FixedValuesGenerator<T>>(values)); + } + + template<typename T> + class Generators : public IGenerator<T> { + std::vector<GeneratorWrapper<T>> m_generators; + size_t m_current = 0; + + void add_generator( GeneratorWrapper<T>&& generator ) { + m_generators.emplace_back( CATCH_MOVE( generator ) ); + } + void add_generator( T const& val ) { + m_generators.emplace_back( value( val ) ); + } + void add_generator( T&& val ) { + m_generators.emplace_back( value( CATCH_MOVE( val ) ) ); + } + template <typename U> + std::enable_if_t<!std::is_same<std::decay_t<U>, T>::value> + add_generator( U&& val ) { + add_generator( T( CATCH_FORWARD( val ) ) ); + } + + template <typename U> void add_generators( U&& valueOrGenerator ) { + add_generator( CATCH_FORWARD( valueOrGenerator ) ); + } + + template <typename U, typename... Gs> + void add_generators( U&& valueOrGenerator, Gs&&... moreGenerators ) { + add_generator( CATCH_FORWARD( valueOrGenerator ) ); + add_generators( CATCH_FORWARD( moreGenerators )... ); + } + + public: + template <typename... Gs> + Generators(Gs &&... moreGenerators) { + m_generators.reserve(sizeof...(Gs)); + add_generators(CATCH_FORWARD(moreGenerators)...); + } + + T const& get() const override { + return m_generators[m_current].get(); + } + + bool next() override { + if (m_current >= m_generators.size()) { + return false; + } + const bool current_status = m_generators[m_current].next(); + if (!current_status) { + ++m_current; + } + return m_current < m_generators.size(); + } + }; + + + template <typename... Ts> + GeneratorWrapper<std::tuple<std::decay_t<Ts>...>> + table( std::initializer_list<std::tuple<std::decay_t<Ts>...>> tuples ) { + return values<std::tuple<Ts...>>( tuples ); + } + + // Tag type to signal that a generator sequence should convert arguments to a specific type + template <typename T> + struct as {}; + + template<typename T, typename... Gs> + auto makeGenerators( GeneratorWrapper<T>&& generator, Gs &&... moreGenerators ) -> Generators<T> { + return Generators<T>(CATCH_MOVE(generator), CATCH_FORWARD(moreGenerators)...); + } + template<typename T> + auto makeGenerators( GeneratorWrapper<T>&& generator ) -> Generators<T> { + return Generators<T>(CATCH_MOVE(generator)); + } + template<typename T, typename... Gs> + auto makeGenerators( T&& val, Gs &&... moreGenerators ) -> Generators<std::decay_t<T>> { + return makeGenerators( value( CATCH_FORWARD( val ) ), CATCH_FORWARD( moreGenerators )... ); + } + template<typename T, typename U, typename... Gs> + auto makeGenerators( as<T>, U&& val, Gs &&... moreGenerators ) -> Generators<T> { + return makeGenerators( value( T( CATCH_FORWARD( val ) ) ), CATCH_FORWARD( moreGenerators )... ); + } + + IGeneratorTracker* acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ); + IGeneratorTracker* createGeneratorTracker( StringRef generatorName, + SourceLineInfo lineInfo, + GeneratorBasePtr&& generator ); + + template<typename L> + auto generate( StringRef generatorName, SourceLineInfo const& lineInfo, L const& generatorExpression ) -> typename decltype(generatorExpression())::type { + using UnderlyingType = typename decltype(generatorExpression())::type; + + IGeneratorTracker* tracker = acquireGeneratorTracker( generatorName, lineInfo ); + // Creation of tracker is delayed after generator creation, so + // that constructing generator can fail without breaking everything. + if (!tracker) { + tracker = createGeneratorTracker( + generatorName, + lineInfo, + Catch::Detail::make_unique<Generators<UnderlyingType>>( + generatorExpression() ) ); + } + + auto const& generator = static_cast<IGenerator<UnderlyingType> const&>( *tracker->getGenerator() ); + return generator.get(); + } + +} // namespace Generators +} // namespace Catch + +#define CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL( ... ) #__VA_ARGS__##_catch_sr +#define CATCH_INTERNAL_GENERATOR_STRINGIZE(...) CATCH_INTERNAL_GENERATOR_STRINGIZE_IMPL(__VA_ARGS__) + +#define GENERATE( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [ ]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_COPY( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [=]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) +#define GENERATE_REF( ... ) \ + Catch::Generators::generate( CATCH_INTERNAL_GENERATOR_STRINGIZE(INTERNAL_CATCH_UNIQUE_NAME(generator)), \ + CATCH_INTERNAL_LINEINFO, \ + [&]{ using namespace Catch::Generators; return makeGenerators( __VA_ARGS__ ); } ) //NOLINT(google-build-using-namespace) + +#endif // CATCH_GENERATORS_HPP_INCLUDED + + +#ifndef CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED +#define CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED + + +#include <cassert> + +namespace Catch { +namespace Generators { + + template <typename T> + class TakeGenerator final : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + size_t m_returned = 0; + size_t m_target; + public: + TakeGenerator(size_t target, GeneratorWrapper<T>&& generator): + m_generator(CATCH_MOVE(generator)), + m_target(target) + { + assert(target != 0 && "Empty generators are not allowed"); + } + T const& get() const override { + return m_generator.get(); + } + bool next() override { + ++m_returned; + if (m_returned >= m_target) { + return false; + } + + const auto success = m_generator.next(); + // If the underlying generator does not contain enough values + // then we cut short as well + if (!success) { + m_returned = m_target; + } + return success; + } + }; + + template <typename T> + GeneratorWrapper<T> take(size_t target, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(Catch::Detail::make_unique<TakeGenerator<T>>(target, CATCH_MOVE(generator))); + } + + + template <typename T, typename Predicate> + class FilterGenerator final : public IGenerator<T> { + GeneratorWrapper<T> m_generator; + Predicate m_predicate; + public: + template <typename P = Predicate> + FilterGenerator(P&& pred, GeneratorWrapper<T>&& generator): + m_generator(CATCH_MOVE(generator)), + m_predicate(CATCH_FORWARD(pred)) + { + if (!m_predicate(m_generator.get())) { + // It might happen that there are no values that pass the + // filter. In that case we throw an exception. + auto has_initial_value = next(); + if (!has_initial_value) { + Detail::throw_generator_exception("No valid value found in filtered generator"); + } + } + } + + T const& get() const override { + return m_generator.get(); + } + + bool next() override { + bool success = m_generator.next(); + if (!success) { + return false; + } + while (!m_predicate(m_generator.get()) && (success = m_generator.next()) == true); + return success; + } + }; + + + template <typename T, typename Predicate> + GeneratorWrapper<T> filter(Predicate&& pred, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(Catch::Detail::make_unique<FilterGenerator<T, Predicate>>(CATCH_FORWARD(pred), CATCH_MOVE(generator))); + } + + template <typename T> + class RepeatGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "RepeatGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + GeneratorWrapper<T> m_generator; + mutable std::vector<T> m_returned; + size_t m_target_repeats; + size_t m_current_repeat = 0; + size_t m_repeat_index = 0; + public: + RepeatGenerator(size_t repeats, GeneratorWrapper<T>&& generator): + m_generator(CATCH_MOVE(generator)), + m_target_repeats(repeats) + { + assert(m_target_repeats > 0 && "Repeat generator must repeat at least once"); + } + + T const& get() const override { + if (m_current_repeat == 0) { + m_returned.push_back(m_generator.get()); + return m_returned.back(); + } + return m_returned[m_repeat_index]; + } + + bool next() override { + // There are 2 basic cases: + // 1) We are still reading the generator + // 2) We are reading our own cache + + // In the first case, we need to poke the underlying generator. + // If it happily moves, we are left in that state, otherwise it is time to start reading from our cache + if (m_current_repeat == 0) { + const auto success = m_generator.next(); + if (!success) { + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + + // In the second case, we need to move indices forward and check that we haven't run up against the end + ++m_repeat_index; + if (m_repeat_index == m_returned.size()) { + m_repeat_index = 0; + ++m_current_repeat; + } + return m_current_repeat < m_target_repeats; + } + }; + + template <typename T> + GeneratorWrapper<T> repeat(size_t repeats, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<T>(Catch::Detail::make_unique<RepeatGenerator<T>>(repeats, CATCH_MOVE(generator))); + } + + template <typename T, typename U, typename Func> + class MapGenerator final : public IGenerator<T> { + // TBD: provide static assert for mapping function, for friendly error message + GeneratorWrapper<U> m_generator; + Func m_function; + // To avoid returning dangling reference, we have to save the values + T m_cache; + public: + template <typename F2 = Func> + MapGenerator(F2&& function, GeneratorWrapper<U>&& generator) : + m_generator(CATCH_MOVE(generator)), + m_function(CATCH_FORWARD(function)), + m_cache(m_function(m_generator.get())) + {} + + T const& get() const override { + return m_cache; + } + bool next() override { + const auto success = m_generator.next(); + if (success) { + m_cache = m_function(m_generator.get()); + } + return success; + } + }; + + template <typename Func, typename U, typename T = FunctionReturnType<Func, U>> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator)) + ); + } + + template <typename T, typename U, typename Func> + GeneratorWrapper<T> map(Func&& function, GeneratorWrapper<U>&& generator) { + return GeneratorWrapper<T>( + Catch::Detail::make_unique<MapGenerator<T, U, Func>>(CATCH_FORWARD(function), CATCH_MOVE(generator)) + ); + } + + template <typename T> + class ChunkGenerator final : public IGenerator<std::vector<T>> { + std::vector<T> m_chunk; + size_t m_chunk_size; + GeneratorWrapper<T> m_generator; + bool m_used_up = false; + public: + ChunkGenerator(size_t size, GeneratorWrapper<T> generator) : + m_chunk_size(size), m_generator(CATCH_MOVE(generator)) + { + m_chunk.reserve(m_chunk_size); + if (m_chunk_size != 0) { + m_chunk.push_back(m_generator.get()); + for (size_t i = 1; i < m_chunk_size; ++i) { + if (!m_generator.next()) { + Detail::throw_generator_exception("Not enough values to initialize the first chunk"); + } + m_chunk.push_back(m_generator.get()); + } + } + } + std::vector<T> const& get() const override { + return m_chunk; + } + bool next() override { + m_chunk.clear(); + for (size_t idx = 0; idx < m_chunk_size; ++idx) { + if (!m_generator.next()) { + return false; + } + m_chunk.push_back(m_generator.get()); + } + return true; + } + }; + + template <typename T> + GeneratorWrapper<std::vector<T>> chunk(size_t size, GeneratorWrapper<T>&& generator) { + return GeneratorWrapper<std::vector<T>>( + Catch::Detail::make_unique<ChunkGenerator<T>>(size, CATCH_MOVE(generator)) + ); + } + +} // namespace Generators +} // namespace Catch + + +#endif // CATCH_GENERATORS_ADAPTERS_HPP_INCLUDED + + +#ifndef CATCH_GENERATORS_RANDOM_HPP_INCLUDED +#define CATCH_GENERATORS_RANDOM_HPP_INCLUDED + + + +#ifndef CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED +#define CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + // This is a simple implementation of C++11 Uniform Random Number + // Generator. It does not provide all operators, because Catch2 + // does not use it, but it should behave as expected inside stdlib's + // distributions. + // The implementation is based on the PCG family (http://pcg-random.org) + class SimplePcg32 { + using state_type = std::uint64_t; + public: + using result_type = std::uint32_t; + static constexpr result_type (min)() { + return 0; + } + static constexpr result_type (max)() { + return static_cast<result_type>(-1); + } + + // Provide some default initial state for the default constructor + SimplePcg32():SimplePcg32(0xed743cc4U) {} + + explicit SimplePcg32(result_type seed_); + + void seed(result_type seed_); + void discard(uint64_t skip); + + result_type operator()(); + + private: + friend bool operator==(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + friend bool operator!=(SimplePcg32 const& lhs, SimplePcg32 const& rhs); + + // In theory we also need operator<< and operator>> + // In practice we do not use them, so we will skip them for now + + + std::uint64_t m_state; + // This part of the state determines which "stream" of the numbers + // is chosen -- we take it as a constant for Catch2, so we only + // need to deal with seeding the main state. + // Picked by reading 8 bytes from `/dev/random` :-) + static const std::uint64_t s_inc = (0x13ed0cc53f939476ULL << 1ULL) | 1ULL; + }; + +} // end namespace Catch + +#endif // CATCH_RANDOM_NUMBER_GENERATOR_HPP_INCLUDED + + + +#ifndef CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED +#define CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED + + + + +#ifndef CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED +#define CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED + +#include <climits> +#include <cstddef> +#include <cstdint> +#include <type_traits> + +// Note: We use the usual enable-disable-autodetect dance here even though +// we do not support these in CMake configuration options (yet?). +// It is highly unlikely that we will need to make these actually +// user-configurable, but this will make it simpler if weend up needing +// it, and it provides an escape hatch to the users who need it. +#if defined( __SIZEOF_INT128__ ) +# define CATCH_CONFIG_INTERNAL_UINT128 +// Unlike GCC, MSVC does not polyfill umul as mulh + mul pair on ARM machines. +// Currently we do not bother doing this ourselves, but we could if it became +// important for perf. +#elif defined( _MSC_VER ) && defined( _M_X64 ) +# define CATCH_CONFIG_INTERNAL_MSVC_UMUL128 +#endif + +#if defined( CATCH_CONFIG_INTERNAL_UINT128 ) && \ + !defined( CATCH_CONFIG_NO_UINT128 ) && \ + !defined( CATCH_CONFIG_UINT128 ) +#define CATCH_CONFIG_UINT128 +#endif + +#if defined( CATCH_CONFIG_INTERNAL_MSVC_UMUL128 ) && \ + !defined( CATCH_CONFIG_NO_MSVC_UMUL128 ) && \ + !defined( CATCH_CONFIG_MSVC_UMUL128 ) +# define CATCH_CONFIG_MSVC_UMUL128 +# include <intrin.h> +#endif + + +namespace Catch { + namespace Detail { + + template <std::size_t> + struct SizedUnsignedType; +#define SizedUnsignedTypeHelper( TYPE ) \ + template <> \ + struct SizedUnsignedType<sizeof( TYPE )> { \ + using type = TYPE; \ + } + + SizedUnsignedTypeHelper( std::uint8_t ); + SizedUnsignedTypeHelper( std::uint16_t ); + SizedUnsignedTypeHelper( std::uint32_t ); + SizedUnsignedTypeHelper( std::uint64_t ); +#undef SizedUnsignedTypeHelper + + template <std::size_t sz> + using SizedUnsignedType_t = typename SizedUnsignedType<sz>::type; + + template <typename T> + using DoubleWidthUnsignedType_t = SizedUnsignedType_t<2 * sizeof( T )>; + + template <typename T> + struct ExtendedMultResult { + T upper; + T lower; + constexpr bool operator==( ExtendedMultResult const& rhs ) const { + return upper == rhs.upper && lower == rhs.lower; + } + }; + + /** + * Returns 128 bit result of lhs * rhs using portable C++ code + * + * This implementation is almost twice as fast as naive long multiplication, + * and unlike intrinsic-based approach, it supports constexpr evaluation. + */ + constexpr ExtendedMultResult<std::uint64_t> + extendedMultPortable(std::uint64_t lhs, std::uint64_t rhs) { +#define CarryBits( x ) ( x >> 32 ) +#define Digits( x ) ( x & 0xFF'FF'FF'FF ) + std::uint64_t lhs_low = Digits( lhs ); + std::uint64_t rhs_low = Digits( rhs ); + std::uint64_t low_low = ( lhs_low * rhs_low ); + std::uint64_t high_high = CarryBits( lhs ) * CarryBits( rhs ); + + // We add in carry bits from low-low already + std::uint64_t high_low = + ( CarryBits( lhs ) * rhs_low ) + CarryBits( low_low ); + // Note that we can add only low bits from high_low, to avoid + // overflow with large inputs + std::uint64_t low_high = + ( lhs_low * CarryBits( rhs ) ) + Digits( high_low ); + + return { high_high + CarryBits( high_low ) + CarryBits( low_high ), + ( low_high << 32 ) | Digits( low_low ) }; +#undef CarryBits +#undef Digits + } + + //! Returns 128 bit result of lhs * rhs + inline ExtendedMultResult<std::uint64_t> + extendedMult( std::uint64_t lhs, std::uint64_t rhs ) { +#if defined( CATCH_CONFIG_UINT128 ) + auto result = __uint128_t( lhs ) * __uint128_t( rhs ); + return { static_cast<std::uint64_t>( result >> 64 ), + static_cast<std::uint64_t>( result ) }; +#elif defined( CATCH_CONFIG_MSVC_UMUL128 ) + std::uint64_t high; + std::uint64_t low = _umul128( lhs, rhs, &high ); + return { high, low }; +#else + return extendedMultPortable( lhs, rhs ); +#endif + } + + + template <typename UInt> + constexpr ExtendedMultResult<UInt> extendedMult( UInt lhs, UInt rhs ) { + static_assert( std::is_unsigned<UInt>::value, + "extendedMult can only handle unsigned integers" ); + static_assert( sizeof( UInt ) < sizeof( std::uint64_t ), + "Generic extendedMult can only handle types smaller " + "than uint64_t" ); + using WideType = DoubleWidthUnsignedType_t<UInt>; + + auto result = WideType( lhs ) * WideType( rhs ); + return { + static_cast<UInt>( result >> ( CHAR_BIT * sizeof( UInt ) ) ), + static_cast<UInt>( result & UInt( -1 ) ) }; + } + + + template <typename TargetType, + typename Generator> + std::enable_if_t<sizeof(typename Generator::result_type) >= sizeof(TargetType), + TargetType> fillBitsFrom(Generator& gen) { + using gresult_type = typename Generator::result_type; + static_assert( std::is_unsigned<TargetType>::value, "Only unsigned integers are supported" ); + static_assert( Generator::min() == 0 && + Generator::max() == static_cast<gresult_type>( -1 ), + "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)" ); + + // We want to return the top bits from a generator, as they are + // usually considered higher quality. + constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT; + constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT; + + return static_cast<TargetType>( gen() >> + ( generated_bits - return_bits) ); + } + + template <typename TargetType, + typename Generator> + std::enable_if_t<sizeof(typename Generator::result_type) < sizeof(TargetType), + TargetType> fillBitsFrom(Generator& gen) { + using gresult_type = typename Generator::result_type; + static_assert( std::is_unsigned<TargetType>::value, + "Only unsigned integers are supported" ); + static_assert( Generator::min() == 0 && + Generator::max() == static_cast<gresult_type>( -1 ), + "Generator must be able to output all numbers in its result type (effectively it must be a random bit generator)" ); + + constexpr auto generated_bits = sizeof( gresult_type ) * CHAR_BIT; + constexpr auto return_bits = sizeof( TargetType ) * CHAR_BIT; + std::size_t filled_bits = 0; + TargetType ret = 0; + do { + ret <<= generated_bits; + ret |= gen(); + filled_bits += generated_bits; + } while ( filled_bits < return_bits ); + + return ret; + } + + /* + * Transposes numbers into unsigned type while keeping their ordering + * + * This means that signed types are changed so that the ordering is + * [INT_MIN, ..., -1, 0, ..., INT_MAX], rather than order we would + * get by simple casting ([0, ..., INT_MAX, INT_MIN, ..., -1]) + */ + template <typename OriginalType, typename UnsignedType> + constexpr + std::enable_if_t<std::is_signed<OriginalType>::value, UnsignedType> + transposeToNaturalOrder( UnsignedType in ) { + static_assert( + sizeof( OriginalType ) == sizeof( UnsignedType ), + "reordering requires the same sized types on both sides" ); + static_assert( std::is_unsigned<UnsignedType>::value, + "Input type must be unsigned" ); + // Assuming 2s complement (standardized in current C++), the + // positive and negative numbers are already internally ordered, + // and their difference is in the top bit. Swapping it orders + // them the desired way. + constexpr auto highest_bit = + UnsignedType( 1 ) << ( sizeof( UnsignedType ) * CHAR_BIT - 1 ); + return static_cast<UnsignedType>( in ^ highest_bit ); + } + + + + template <typename OriginalType, + typename UnsignedType> + constexpr + std::enable_if_t<std::is_unsigned<OriginalType>::value, UnsignedType> + transposeToNaturalOrder(UnsignedType in) { + static_assert( + sizeof( OriginalType ) == sizeof( UnsignedType ), + "reordering requires the same sized types on both sides" ); + static_assert( std::is_unsigned<UnsignedType>::value, "Input type must be unsigned" ); + // No reordering is needed for unsigned -> unsigned + return in; + } + } // namespace Detail +} // namespace Catch + +#endif // CATCH_RANDOM_INTEGER_HELPERS_HPP_INCLUDED + +namespace Catch { + +/** + * Implementation of uniform distribution on integers. + * + * Unlike `std::uniform_int_distribution`, this implementation supports + * various 1 byte integral types, including bool (but you should not + * actually use it for bools). + * + * The underlying algorithm is based on the one described in "Fast Random + * Integer Generation in an Interval" by Daniel Lemire, but has been + * optimized under the assumption of reuse of the same distribution object. + */ +template <typename IntegerType> +class uniform_integer_distribution { + static_assert(std::is_integral<IntegerType>::value, "..."); + + using UnsignedIntegerType = Detail::SizedUnsignedType_t<sizeof(IntegerType)>; + + // Only the left bound is stored, and we store it converted to its + // unsigned image. This avoids having to do the conversions inside + // the operator(), at the cost of having to do the conversion in + // the a() getter. The right bound is only needed in the b() getter, + // so we recompute it there from other stored data. + UnsignedIntegerType m_a; + + // How many different values are there in [a, b]. a == b => 1, can be 0 for distribution over all values in the type. + UnsignedIntegerType m_ab_distance; + + // We hoisted this out of the main generation function. Technically, + // this means that using this distribution will be slower than Lemire's + // algorithm if this distribution instance will be used only few times, + // but it will be faster if it is used many times. Since Catch2 uses + // distributions only to implement random generators, we assume that each + // distribution will be reused many times and this is an optimization. + UnsignedIntegerType m_rejection_threshold = 0; + + static constexpr UnsignedIntegerType computeDistance(IntegerType a, IntegerType b) { + // This overflows and returns 0 if a == 0 and b == TYPE_MAX. + // We handle that later when generating the number. + return transposeTo(b) - transposeTo(a) + 1; + } + + static constexpr UnsignedIntegerType computeRejectionThreshold(UnsignedIntegerType ab_distance) { + // distance == 0 means that we will return all possible values from + // the type's range, and that we shouldn't reject anything. + if ( ab_distance == 0 ) { return 0; } + return ( ~ab_distance + 1 ) % ab_distance; + } + + static constexpr UnsignedIntegerType transposeTo(IntegerType in) { + return Detail::transposeToNaturalOrder<IntegerType>( + static_cast<UnsignedIntegerType>( in ) ); + } + static constexpr IntegerType transposeBack(UnsignedIntegerType in) { + return static_cast<IntegerType>( + Detail::transposeToNaturalOrder<IntegerType>(in) ); + } + +public: + using result_type = IntegerType; + + constexpr uniform_integer_distribution( IntegerType a, IntegerType b ): + m_a( transposeTo(a) ), + m_ab_distance( computeDistance(a, b) ), + m_rejection_threshold( computeRejectionThreshold(m_ab_distance) ) { + assert( a <= b ); + } + + template <typename Generator> + constexpr result_type operator()( Generator& g ) { + // All possible values of result_type are valid. + if ( m_ab_distance == 0 ) { + return transposeBack( Detail::fillBitsFrom<UnsignedIntegerType>( g ) ); + } + + auto random_number = Detail::fillBitsFrom<UnsignedIntegerType>( g ); + auto emul = Detail::extendedMult( random_number, m_ab_distance ); + // Unlike Lemire's algorithm we skip the ab_distance check, since + // we precomputed the rejection threshold, which is always tighter. + while (emul.lower < m_rejection_threshold) { + random_number = Detail::fillBitsFrom<UnsignedIntegerType>( g ); + emul = Detail::extendedMult( random_number, m_ab_distance ); + } + + return transposeBack(m_a + emul.upper); + } + + constexpr result_type a() const { return transposeBack(m_a); } + constexpr result_type b() const { return transposeBack(m_ab_distance + m_a - 1); } +}; + +} // end namespace Catch + +#endif // CATCH_UNIFORM_INTEGER_DISTRIBUTION_HPP_INCLUDED + + + +#ifndef CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED +#define CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED + + + + +#ifndef CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED + + + +#ifndef CATCH_POLYFILLS_HPP_INCLUDED +#define CATCH_POLYFILLS_HPP_INCLUDED + +namespace Catch { + + bool isnan(float f); + bool isnan(double d); + + float nextafter(float x, float y); + double nextafter(double x, double y); + +} + +#endif // CATCH_POLYFILLS_HPP_INCLUDED + +#include <cassert> +#include <cmath> +#include <cstdint> +#include <limits> +#include <type_traits> + +namespace Catch { + + namespace Detail { + /** + * Returns the largest magnitude of 1-ULP distance inside the [a, b] range. + * + * Assumes `a < b`. + */ + template <typename FloatType> + FloatType gamma(FloatType a, FloatType b) { + static_assert( std::is_floating_point<FloatType>::value, + "gamma returns the largest ULP magnitude within " + "floating point range [a, b]. This only makes sense " + "for floating point types" ); + assert( a <= b ); + + const auto gamma_up = Catch::nextafter( a, std::numeric_limits<FloatType>::infinity() ) - a; + const auto gamma_down = b - Catch::nextafter( b, -std::numeric_limits<FloatType>::infinity() ); + + return gamma_up < gamma_down ? gamma_down : gamma_up; + } + + template <typename FloatingPoint> + struct DistanceTypePicker; + template <> + struct DistanceTypePicker<float> { + using type = std::uint32_t; + }; + template <> + struct DistanceTypePicker<double> { + using type = std::uint64_t; + }; + + template <typename T> + using DistanceType = typename DistanceTypePicker<T>::type; + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + /** + * Computes the number of equi-distant floats in [a, b] + * + * Since not every range can be split into equidistant floats + * exactly, we actually compute ceil(b/distance - a/distance), + * because in those cases we want to overcount. + * + * Uses modified Dekker's FastTwoSum algorithm to handle rounding. + */ + template <typename FloatType> + DistanceType<FloatType> + count_equidistant_floats( FloatType a, FloatType b, FloatType distance ) { + assert( a <= b ); + // We get distance as gamma for our uniform float distribution, + // so this will round perfectly. + const auto ag = a / distance; + const auto bg = b / distance; + + const auto s = bg - ag; + const auto err = ( std::fabs( a ) <= std::fabs( b ) ) + ? -ag - ( s - bg ) + : bg - ( s + ag ); + const auto ceil_s = static_cast<DistanceType<FloatType>>( std::ceil( s ) ); + + return ( ceil_s != s ) ? ceil_s : ceil_s + ( err > 0 ); + } +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + + } + +} // end namespace Catch + +#endif // CATCH_RANDOM_FLOATING_POINT_HELPERS_HPP_INCLUDED + +#include <cmath> +#include <type_traits> + +namespace Catch { + + namespace Detail { +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + // The issue with overflow only happens with maximal ULP and HUGE + // distance, e.g. when generating numbers in [-inf, inf] for given + // type. So we only check for the largest possible ULP in the + // type, and return something that does not overflow to inf in 1 mult. + constexpr std::uint64_t calculate_max_steps_in_one_go(double gamma) { + if ( gamma == 1.99584030953472e+292 ) { return 9007199254740991; } + return static_cast<std::uint64_t>( -1 ); + } + constexpr std::uint32_t calculate_max_steps_in_one_go(float gamma) { + if ( gamma == 2.028241e+31f ) { return 16777215; } + return static_cast<std::uint32_t>( -1 ); + } +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + } + +/** + * Implementation of uniform distribution on floating point numbers. + * + * Note that we support only `float` and `double` types, because these + * usually mean the same thing across different platform. `long double` + * varies wildly by platform and thus we cannot provide reproducible + * implementation. Also note that we don't implement all parts of + * distribution per standard: this distribution is not serializable, nor + * can the range be arbitrarily reset. + * + * The implementation also uses different approach than the one taken by + * `std::uniform_real_distribution`, where instead of generating a number + * between [0, 1) and then multiplying the range bounds with it, we first + * split the [a, b] range into a set of equidistributed floating point + * numbers, and then use uniform int distribution to pick which one to + * return. + * + * This has the advantage of guaranteeing uniformity (the multiplication + * method loses uniformity due to rounding when multiplying floats), except + * for small non-uniformity at one side of the interval, where we have + * to deal with the fact that not every interval is splittable into + * equidistributed floats. + * + * Based on "Drawing random floating-point numbers from an interval" by + * Frederic Goualard. + */ +template <typename FloatType> +class uniform_floating_point_distribution { + static_assert(std::is_floating_point<FloatType>::value, "..."); + static_assert(!std::is_same<FloatType, long double>::value, + "We do not support long double due to inconsistent behaviour between platforms"); + + using WidthType = Detail::DistanceType<FloatType>; + + FloatType m_a, m_b; + FloatType m_ulp_magnitude; + WidthType m_floats_in_range; + uniform_integer_distribution<WidthType> m_int_dist; + + // In specific cases, we can overflow into `inf` when computing the + // `steps * g` offset. To avoid this, we don't offset by more than this + // in one multiply + addition. + WidthType m_max_steps_in_one_go; + // We don't want to do the magnitude check every call to `operator()` + bool m_a_has_leq_magnitude; + +public: + using result_type = FloatType; + + uniform_floating_point_distribution( FloatType a, FloatType b ): + m_a( a ), + m_b( b ), + m_ulp_magnitude( Detail::gamma( m_a, m_b ) ), + m_floats_in_range( Detail::count_equidistant_floats( m_a, m_b, m_ulp_magnitude ) ), + m_int_dist(0, m_floats_in_range), + m_max_steps_in_one_go( Detail::calculate_max_steps_in_one_go(m_ulp_magnitude)), + m_a_has_leq_magnitude(std::fabs(m_a) <= std::fabs(m_b)) + { + assert( a <= b ); + } + + template <typename Generator> + result_type operator()( Generator& g ) { + WidthType steps = m_int_dist( g ); + if ( m_a_has_leq_magnitude ) { + if ( steps == m_floats_in_range ) { return m_a; } + auto b = m_b; + while (steps > m_max_steps_in_one_go) { + b -= m_max_steps_in_one_go * m_ulp_magnitude; + steps -= m_max_steps_in_one_go; + } + return b - steps * m_ulp_magnitude; + } else { + if ( steps == m_floats_in_range ) { return m_b; } + auto a = m_a; + while (steps > m_max_steps_in_one_go) { + a += m_max_steps_in_one_go * m_ulp_magnitude; + steps -= m_max_steps_in_one_go; + } + return a + steps * m_ulp_magnitude; + } + } + + result_type a() const { return m_a; } + result_type b() const { return m_b; } +}; + +} // end namespace Catch + +#endif // CATCH_UNIFORM_FLOATING_POINT_DISTRIBUTION_HPP_INCLUDED + +namespace Catch { +namespace Generators { +namespace Detail { + // Returns a suitable seed for a random floating generator based off + // the primary internal rng. It does so by taking current value from + // the rng and returning it as the seed. + std::uint32_t getSeed(); +} + +template <typename Float> +class RandomFloatingGenerator final : public IGenerator<Float> { + Catch::SimplePcg32 m_rng; + Catch::uniform_floating_point_distribution<Float> m_dist; + Float m_current_number; +public: + RandomFloatingGenerator( Float a, Float b, std::uint32_t seed ): + m_rng(seed), + m_dist(a, b) { + static_cast<void>(next()); + } + + Float const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } +}; + +template <> +class RandomFloatingGenerator<long double> final : public IGenerator<long double> { + // We still rely on <random> for this specialization, but we don't + // want to drag it into the header. + struct PImpl; + Catch::Detail::unique_ptr<PImpl> m_pimpl; + long double m_current_number; + +public: + RandomFloatingGenerator( long double a, long double b, std::uint32_t seed ); + + long double const& get() const override { return m_current_number; } + bool next() override; + + ~RandomFloatingGenerator() override; // = default +}; + +template <typename Integer> +class RandomIntegerGenerator final : public IGenerator<Integer> { + Catch::SimplePcg32 m_rng; + Catch::uniform_integer_distribution<Integer> m_dist; + Integer m_current_number; +public: + RandomIntegerGenerator( Integer a, Integer b, std::uint32_t seed ): + m_rng(seed), + m_dist(a, b) { + static_cast<void>(next()); + } + + Integer const& get() const override { + return m_current_number; + } + bool next() override { + m_current_number = m_dist(m_rng); + return true; + } +}; + +template <typename T> +std::enable_if_t<std::is_integral<T>::value, GeneratorWrapper<T>> +random(T a, T b) { + return GeneratorWrapper<T>( + Catch::Detail::make_unique<RandomIntegerGenerator<T>>(a, b, Detail::getSeed()) + ); +} + +template <typename T> +std::enable_if_t<std::is_floating_point<T>::value, +GeneratorWrapper<T>> +random(T a, T b) { + return GeneratorWrapper<T>( + Catch::Detail::make_unique<RandomFloatingGenerator<T>>(a, b, Detail::getSeed()) + ); +} + + +} // namespace Generators +} // namespace Catch + + +#endif // CATCH_GENERATORS_RANDOM_HPP_INCLUDED + + +#ifndef CATCH_GENERATORS_RANGE_HPP_INCLUDED +#define CATCH_GENERATORS_RANGE_HPP_INCLUDED + + +#include <iterator> +#include <type_traits> + +namespace Catch { +namespace Generators { + + +template <typename T> +class RangeGenerator final : public IGenerator<T> { + T m_current; + T m_end; + T m_step; + bool m_positive; + +public: + RangeGenerator(T const& start, T const& end, T const& step): + m_current(start), + m_end(end), + m_step(step), + m_positive(m_step > T(0)) + { + assert(m_current != m_end && "Range start and end cannot be equal"); + assert(m_step != T(0) && "Step size cannot be zero"); + assert(((m_positive && m_current <= m_end) || (!m_positive && m_current >= m_end)) && "Step moves away from end"); + } + + RangeGenerator(T const& start, T const& end): + RangeGenerator(start, end, (start < end) ? T(1) : T(-1)) + {} + + T const& get() const override { + return m_current; + } + + bool next() override { + m_current += m_step; + return (m_positive) ? (m_current < m_end) : (m_current > m_end); + } +}; + +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end, T const& step) { + static_assert(std::is_arithmetic<T>::value && !std::is_same<T, bool>::value, "Type must be numeric"); + return GeneratorWrapper<T>(Catch::Detail::make_unique<RangeGenerator<T>>(start, end, step)); +} + +template <typename T> +GeneratorWrapper<T> range(T const& start, T const& end) { + static_assert(std::is_integral<T>::value && !std::is_same<T, bool>::value, "Type must be an integer"); + return GeneratorWrapper<T>(Catch::Detail::make_unique<RangeGenerator<T>>(start, end)); +} + + +template <typename T> +class IteratorGenerator final : public IGenerator<T> { + static_assert(!std::is_same<T, bool>::value, + "IteratorGenerator currently does not support bools" + "because of std::vector<bool> specialization"); + + std::vector<T> m_elems; + size_t m_current = 0; +public: + template <typename InputIterator, typename InputSentinel> + IteratorGenerator(InputIterator first, InputSentinel last):m_elems(first, last) { + if (m_elems.empty()) { + Detail::throw_generator_exception("IteratorGenerator received no valid values"); + } + } + + T const& get() const override { + return m_elems[m_current]; + } + + bool next() override { + ++m_current; + return m_current != m_elems.size(); + } +}; + +template <typename InputIterator, + typename InputSentinel, + typename ResultType = std::remove_const_t<typename std::iterator_traits<InputIterator>::value_type>> +GeneratorWrapper<ResultType> from_range(InputIterator from, InputSentinel to) { + return GeneratorWrapper<ResultType>(Catch::Detail::make_unique<IteratorGenerator<ResultType>>(from, to)); +} + +template <typename Container> +auto from_range(Container const& cnt) { + using std::begin; + using std::end; + return from_range( begin( cnt ), end( cnt ) ); +} + + +} // namespace Generators +} // namespace Catch + + +#endif // CATCH_GENERATORS_RANGE_HPP_INCLUDED + +#endif // CATCH_GENERATORS_ALL_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2's interfaces. It includes + * **all** of Catch2 headers related to interfaces. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of somewhat increased compilation + * times. + * + * When a new header is added to either the `interfaces` folder, or to + * the corresponding internal subfolder, it should be added here. + */ + + +#ifndef CATCH_INTERFACES_ALL_HPP_INCLUDED +#define CATCH_INTERFACES_ALL_HPP_INCLUDED + + + +#ifndef CATCH_INTERFACES_REPORTER_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_HPP_INCLUDED + + + +#ifndef CATCH_TEST_RUN_INFO_HPP_INCLUDED +#define CATCH_TEST_RUN_INFO_HPP_INCLUDED + + +namespace Catch { + + struct TestRunInfo { + constexpr TestRunInfo(StringRef _name) : name(_name) {} + StringRef name; + }; + +} // end namespace Catch + +#endif // CATCH_TEST_RUN_INFO_HPP_INCLUDED + +#include <map> +#include <string> +#include <vector> +#include <iosfwd> + +namespace Catch { + + struct ReporterDescription; + struct ListenerDescription; + struct TagInfo; + struct TestCaseInfo; + class TestCaseHandle; + class IConfig; + class IStream; + enum class ColourMode : std::uint8_t; + + struct ReporterConfig { + ReporterConfig( IConfig const* _fullConfig, + Detail::unique_ptr<IStream> _stream, + ColourMode colourMode, + std::map<std::string, std::string> customOptions ); + + ReporterConfig( ReporterConfig&& ) = default; + ReporterConfig& operator=( ReporterConfig&& ) = default; + ~ReporterConfig(); // = default + + Detail::unique_ptr<IStream> takeStream() &&; + IConfig const* fullConfig() const; + ColourMode colourMode() const; + std::map<std::string, std::string> const& customOptions() const; + + private: + Detail::unique_ptr<IStream> m_stream; + IConfig const* m_fullConfig; + ColourMode m_colourMode; + std::map<std::string, std::string> m_customOptions; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ); + + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = delete; + AssertionStats& operator = ( AssertionStats && ) = delete; + + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo&& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string&& _stdOut, + std::string&& _stdErr, + bool _aborting ); + + TestCaseInfo const * testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + //! By setting up its preferences, a reporter can modify Catch2's behaviour + //! in some regards, e.g. it can request Catch2 to capture writes to + //! stdout/stderr during test execution, and pass them to the reporter. + struct ReporterPreferences { + //! Catch2 should redirect writes to stdout and pass them to the + //! reporter + bool shouldRedirectStdOut = false; + //! Catch2 should call `Reporter::assertionEnded` even for passing + //! assertions + bool shouldReportAllAssertions = false; + }; + + /** + * The common base for all reporters and event listeners + * + * Implementing classes must also implement: + * + * //! User-friendly description of the reporter/listener type + * static std::string getDescription() + * + * Generally shouldn't be derived from by users of Catch2 directly, + * instead they should derive from one of the utility bases that + * derive from this class. + */ + class IEventListener { + protected: + //! Derived classes can set up their preferences here + ReporterPreferences m_preferences; + //! The test run's config as filled in from CLI and defaults + IConfig const* m_config; + + public: + IEventListener( IConfig const* config ): m_config( config ) {} + + virtual ~IEventListener(); // = default; + + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + + ReporterPreferences const& getPreferences() const { + return m_preferences; + } + + //! Called when no test cases match provided test spec + virtual void noMatchingTestCases( StringRef unmatchedSpec ) = 0; + //! Called for all invalid test specs from the cli + virtual void reportInvalidTestSpec( StringRef invalidArgument ) = 0; + + /** + * Called once in a testing run before tests are started + * + * Not called if tests won't be run (e.g. only listing will happen) + */ + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialStarting( TestCaseInfo const& testInfo, uint64_t partNumber ) = 0; + //! Called when a `SECTION` is being entered. Not called for skipped sections + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + //! Called when user-code is being probed before the actual benchmark runs + virtual void benchmarkPreparing( StringRef benchmarkName ) = 0; + //! Called after probe but before the user-code is being benchmarked + virtual void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) = 0; + //! Called with the benchmark results if benchmark successfully finishes + virtual void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) = 0; + //! Called if running the benchmarks fails for any reason + virtual void benchmarkFailed( StringRef benchmarkName ) = 0; + + //! Called before assertion success/failure is evaluated + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + //! Called after assertion was fully evaluated + virtual void assertionEnded( AssertionStats const& assertionStats ) = 0; + + //! Called after a `SECTION` has finished running + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + //! Called _every time_ a TEST_CASE is entered, including repeats (due to sections) + virtual void testCasePartialEnded(TestCaseStats const& testCaseStats, uint64_t partNumber ) = 0; + //! Called _once_ for each TEST_CASE, no matter how many times it is entered + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + /** + * Called once after all tests in a testing run are finished + * + * Not called if tests weren't run (e.g. only listings happened) + */ + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + /** + * Called with test cases that are skipped due to the test run aborting. + * NOT called for test cases that are explicitly skipped using the `SKIP` macro. + * + * Deprecated - will be removed in the next major release. + */ + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + + //! Called if a fatal error (signal/structured exception) occurred + virtual void fatalErrorEncountered( StringRef error ) = 0; + + //! Writes out information about provided reporters using reporter-specific format + virtual void listReporters(std::vector<ReporterDescription> const& descriptions) = 0; + //! Writes out the provided listeners descriptions using reporter-specific format + virtual void listListeners(std::vector<ListenerDescription> const& descriptions) = 0; + //! Writes out information about provided tests using reporter-specific format + virtual void listTests(std::vector<TestCaseHandle> const& tests) = 0; + //! Writes out information about the provided tags using reporter-specific format + virtual void listTags(std::vector<TagInfo> const& tags) = 0; + }; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + +} // end namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED +#define CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + struct ReporterConfig; + class IConfig; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + + + class IReporterFactory { + public: + virtual ~IReporterFactory(); // = default + + virtual IEventListenerPtr + create( ReporterConfig&& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; + + class EventListenerFactory { + public: + virtual ~EventListenerFactory(); // = default + virtual IEventListenerPtr create( IConfig const* config ) const = 0; + //! Return a meaningful name for the listener, e.g. its type name + virtual StringRef getName() const = 0; + //! Return listener's description if available + virtual std::string getDescription() const = 0; + }; +} // namespace Catch + +#endif // CATCH_INTERFACES_REPORTER_FACTORY_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#define CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +#include <string> + +namespace Catch { + + struct TagAlias; + + class ITagAliasRegistry { + public: + virtual ~ITagAliasRegistry(); // = default + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +#endif // CATCH_INTERFACES_TAG_ALIAS_REGISTRY_HPP_INCLUDED + + +#ifndef CATCH_INTERFACES_TESTCASE_HPP_INCLUDED +#define CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#include <vector> + +namespace Catch { + + struct TestCaseInfo; + class TestCaseHandle; + class IConfig; + + class ITestCaseRegistry { + public: + virtual ~ITestCaseRegistry(); // = default + // TODO: this exists only for adding filenames to test cases -- let's expose this in a saner way later + virtual std::vector<TestCaseInfo* > const& getAllInfos() const = 0; + virtual std::vector<TestCaseHandle> const& getAllTests() const = 0; + virtual std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + +} + +#endif // CATCH_INTERFACES_TESTCASE_HPP_INCLUDED + +#endif // CATCH_INTERFACES_ALL_HPP_INCLUDED + + +#ifndef CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED +#define CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + + +namespace Catch { + namespace Detail { + //! Provides case-insensitive `op<` semantics when called + struct CaseInsensitiveLess { + bool operator()( StringRef lhs, + StringRef rhs ) const; + }; + + //! Provides case-insensitive `op==` semantics when called + struct CaseInsensitiveEqualTo { + bool operator()( StringRef lhs, + StringRef rhs ) const; + }; + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_CASE_INSENSITIVE_COMPARISONS_HPP_INCLUDED + + + +/** \file + * Wrapper for ANDROID_LOGWRITE configuration option + * + * We want to default to enabling it when compiled for android, but + * users of the library should also be able to disable it if they want + * to. + */ + +#ifndef CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED +#define CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + + +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + + +#if defined( CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE ) && \ + !defined( CATCH_CONFIG_NO_ANDROID_LOGWRITE ) && \ + !defined( CATCH_CONFIG_ANDROID_LOGWRITE ) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#endif // CATCH_CONFIG_ANDROID_LOGWRITE_HPP_INCLUDED + + + +/** \file + * Wrapper for UNCAUGHT_EXCEPTIONS configuration option + * + * For some functionality, Catch2 requires to know whether there is + * an active exception. Because `std::uncaught_exception` is deprecated + * in C++17, we want to use `std::uncaught_exceptions` if possible. + */ + +#ifndef CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED +#define CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + + +#if defined(_MSC_VER) +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif +#endif + + +#include <exception> + +#if defined(__cpp_lib_uncaught_exceptions) \ + && !defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif // __cpp_lib_uncaught_exceptions + + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) \ + && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) + +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + + +#endif // CATCH_CONFIG_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + + +#ifndef CATCH_CONSOLE_COLOUR_HPP_INCLUDED +#define CATCH_CONSOLE_COLOUR_HPP_INCLUDED + + +#include <iosfwd> +#include <cstdint> + +namespace Catch { + + enum class ColourMode : std::uint8_t; + class IStream; + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + BrightYellow = Bright | Yellow, + + // By intention + FileName = LightGrey, + Warning = BrightYellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + Skip = LightGrey, + + OriginalExpression = Cyan, + ReconstructedExpression = BrightYellow, + + SecondaryText = LightGrey, + Headers = White + }; + }; + + class ColourImpl { + protected: + //! The associated stream of this ColourImpl instance + IStream* m_stream; + public: + ColourImpl( IStream* stream ): m_stream( stream ) {} + + //! RAII wrapper around writing specific colour of text using specific + //! colour impl into a stream. + class ColourGuard { + ColourImpl const* m_colourImpl; + Colour::Code m_code; + bool m_engaged = false; + + public: + //! Does **not** engage the guard/start the colour + ColourGuard( Colour::Code code, + ColourImpl const* colour ); + + ColourGuard( ColourGuard const& rhs ) = delete; + ColourGuard& operator=( ColourGuard const& rhs ) = delete; + + ColourGuard( ColourGuard&& rhs ) noexcept; + ColourGuard& operator=( ColourGuard&& rhs ) noexcept; + + //! Removes colour _if_ the guard was engaged + ~ColourGuard(); + + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard& engage( std::ostream& stream ) &; + /** + * Explicitly engages colour for given stream. + * + * The API based on operator<< should be preferred. + */ + ColourGuard&& engage( std::ostream& stream ) &&; + + private: + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard& guard ) { + guard.engageImpl( lhs ); + return lhs; + } + //! Engages the guard and starts using colour + friend std::ostream& operator<<( std::ostream& lhs, + ColourGuard&& guard) { + guard.engageImpl( lhs ); + return lhs; + } + + void engageImpl( std::ostream& stream ); + + }; + + virtual ~ColourImpl(); // = default + /** + * Creates a guard object for given colour and this colour impl + * + * **Important:** + * the guard starts disengaged, and has to be engaged explicitly. + */ + ColourGuard guardColour( Colour::Code colourCode ); + + private: + virtual void use( Colour::Code colourCode ) const = 0; + }; + + //! Provides ColourImpl based on global config and target compilation platform + Detail::unique_ptr<ColourImpl> makeColourImpl( ColourMode colourSelection, + IStream* stream ); + + //! Checks if specific colour impl has been compiled into the binary + bool isColourImplAvailable( ColourMode colourSelection ); + +} // end namespace Catch + +#endif // CATCH_CONSOLE_COLOUR_HPP_INCLUDED + + +#ifndef CATCH_CONSOLE_WIDTH_HPP_INCLUDED +#define CATCH_CONSOLE_WIDTH_HPP_INCLUDED + +// This include must be kept so that user's configured value for CONSOLE_WIDTH +// is used before we attempt to provide a default value + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +#endif // CATCH_CONSOLE_WIDTH_HPP_INCLUDED + + +#ifndef CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED +#define CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED + + +#include <cstddef> +#include <initializer_list> + +// We want a simple polyfill over `std::empty`, `std::size` and so on +// for C++14 or C++ libraries with incomplete support. +// We also have to handle that MSVC std lib will happily provide these +// under older standards. +#if defined(CATCH_CPP17_OR_GREATER) || defined(_MSC_VER) + +// We are already using this header either way, so there shouldn't +// be much additional overhead in including it to get the feature +// test macros +#include <string> + +# if !defined(__cpp_lib_nonmember_container_access) +# define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +# endif + +#else +#define CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS +#endif + + + +namespace Catch { +namespace Detail { + +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + template <typename Container> + constexpr auto empty(Container const& cont) -> decltype(cont.empty()) { + return cont.empty(); + } + template <typename T, std::size_t N> + constexpr bool empty(const T (&)[N]) noexcept { + // GCC < 7 does not support the const T(&)[] parameter syntax + // so we have to ignore the length explicitly + (void)N; + return false; + } + template <typename T> + constexpr bool empty(std::initializer_list<T> list) noexcept { + return list.size() > 0; + } + + + template <typename Container> + constexpr auto size(Container const& cont) -> decltype(cont.size()) { + return cont.size(); + } + template <typename T, std::size_t N> + constexpr std::size_t size(const T(&)[N]) noexcept { + return N; + } +#endif // CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS + +} // end namespace Detail +} // end namespace Catch + + + +#endif // CATCH_CONTAINER_NONMEMBERS_HPP_INCLUDED + + +#ifndef CATCH_DEBUG_CONSOLE_HPP_INCLUDED +#define CATCH_DEBUG_CONSOLE_HPP_INCLUDED + +#include <string> + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} + +#endif // CATCH_DEBUG_CONSOLE_HPP_INCLUDED + + +#ifndef CATCH_DEBUGGER_HPP_INCLUDED +#define CATCH_DEBUGGER_HPP_INCLUDED + + +namespace Catch { + bool isDebuggerActive(); +} + +#ifdef CATCH_PLATFORM_MAC + + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd43e0000") + #elif defined(__POWERPC__) + #define CATCH_TRAP() __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ) /* NOLINT */ + #endif + +#elif defined(CATCH_PLATFORM_IPHONE) + + // use inline assembler + #if defined(__i386__) || defined(__x86_64__) + #define CATCH_TRAP() __asm__("int $3") + #elif defined(__aarch64__) + #define CATCH_TRAP() __asm__(".inst 0xd4200000") + #elif defined(__arm__) && !defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xe7f001f0") + #elif defined(__arm__) && defined(__thumb__) + #define CATCH_TRAP() __asm__(".inst 0xde01") + #endif + +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include <signal.h> + + #define CATCH_TRAP() raise(SIGTRAP) + #endif +#elif defined(_MSC_VER) + #define CATCH_TRAP() __debugbreak() +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_TRAP() DebugBreak() +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER + #ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() []{ if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } }() + #else + #define CATCH_BREAK_INTO_DEBUGGER() []{}() + #endif +#endif + +#endif // CATCH_DEBUGGER_HPP_INCLUDED + + +#ifndef CATCH_ENFORCE_HPP_INCLUDED +#define CATCH_ENFORCE_HPP_INCLUDED + + +#include <exception> + +namespace Catch { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + template <typename Ex> + [[noreturn]] + void throw_exception(Ex const& e) { + throw e; + } +#else // ^^ Exceptions are enabled // Exceptions are disabled vv + [[noreturn]] + void throw_exception(std::exception const& e); +#endif + + [[noreturn]] + void throw_logic_error(std::string const& msg); + [[noreturn]] + void throw_domain_error(std::string const& msg); + [[noreturn]] + void throw_runtime_error(std::string const& msg); + +} // namespace Catch; + +#define CATCH_MAKE_MSG(...) \ + (Catch::ReusableStringStream() << __VA_ARGS__).str() + +#define CATCH_INTERNAL_ERROR(...) \ + Catch::throw_logic_error(CATCH_MAKE_MSG( CATCH_INTERNAL_LINEINFO << ": Internal Catch2 error: " << __VA_ARGS__)) + +#define CATCH_ERROR(...) \ + Catch::throw_domain_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_RUNTIME_ERROR(...) \ + Catch::throw_runtime_error(CATCH_MAKE_MSG( __VA_ARGS__ )) + +#define CATCH_ENFORCE( condition, ... ) \ + do{ if( !(condition) ) CATCH_ERROR( __VA_ARGS__ ); } while(false) + + +#endif // CATCH_ENFORCE_HPP_INCLUDED + + +#ifndef CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED +#define CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#include <vector> + +namespace Catch { + + namespace Detail { + + Catch::Detail::unique_ptr<EnumInfo> makeEnumInfo( StringRef enumName, StringRef allValueNames, std::vector<int> const& values ); + + class EnumValuesRegistry : public IMutableEnumValuesRegistry { + + std::vector<Catch::Detail::unique_ptr<EnumInfo>> m_enumInfos; + + EnumInfo const& registerEnum( StringRef enumName, StringRef allValueNames, std::vector<int> const& values) override; + }; + + std::vector<StringRef> parseEnums( StringRef enums ); + + } // Detail + +} // Catch + +#endif // CATCH_ENUM_VALUES_REGISTRY_HPP_INCLUDED + + +#ifndef CATCH_ERRNO_GUARD_HPP_INCLUDED +#define CATCH_ERRNO_GUARD_HPP_INCLUDED + +namespace Catch { + + //! Simple RAII class that stores the value of `errno` + //! at construction and restores it at destruction. + class ErrnoGuard { + public: + // Keep these outlined to avoid dragging in macros from <cerrno> + + ErrnoGuard(); + ~ErrnoGuard(); + private: + int m_oldErrno; + }; + +} + +#endif // CATCH_ERRNO_GUARD_HPP_INCLUDED + + +#ifndef CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED +#define CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + + +#include <vector> +#include <string> + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() override; + void registerTranslator( Detail::unique_ptr<IExceptionTranslator>&& translator ); + std::string translateActiveException() const override; + + private: + ExceptionTranslators m_translators; + }; +} + +#endif // CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + + +#ifndef CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED +#define CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED + +#include <cassert> + +namespace Catch { + + /** + * Wrapper for platform-specific fatal error (signals/SEH) handlers + * + * Tries to be cooperative with other handlers, and not step over + * other handlers. This means that unknown structured exceptions + * are passed on, previous signal handlers are called, and so on. + * + * Can only be instantiated once, and assumes that once a signal + * is caught, the binary will end up terminating. Thus, there + */ + class FatalConditionHandler { + bool m_started = false; + + // Install/disengage implementation for specific platform. + // Should be if-defed to work on current platform, can assume + // engage-disengage 1:1 pairing. + void engage_platform(); + void disengage_platform() noexcept; + public: + // Should also have platform-specific implementations as needed + FatalConditionHandler(); + ~FatalConditionHandler(); + + void engage() { + assert(!m_started && "Handler cannot be installed twice."); + m_started = true; + engage_platform(); + } + + void disengage() noexcept { + assert(m_started && "Handler cannot be uninstalled without being installed first"); + m_started = false; + disengage_platform(); + } + }; + + //! Simple RAII guard for (dis)engaging the FatalConditionHandler + class FatalConditionHandlerGuard { + FatalConditionHandler* m_handler; + public: + FatalConditionHandlerGuard(FatalConditionHandler* handler): + m_handler(handler) { + m_handler->engage(); + } + ~FatalConditionHandlerGuard() { + m_handler->disengage(); + } + }; + +} // end namespace Catch + +#endif // CATCH_FATAL_CONDITION_HANDLER_HPP_INCLUDED + + +#ifndef CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED +#define CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED + + +#include <cassert> +#include <cmath> +#include <cstdint> +#include <utility> +#include <limits> + +namespace Catch { + namespace Detail { + + uint32_t convertToBits(float f); + uint64_t convertToBits(double d); + + // Used when we know we want == comparison of two doubles + // to centralize warning suppression + bool directCompare( float lhs, float rhs ); + bool directCompare( double lhs, double rhs ); + + } // end namespace Detail + + + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic push + // We do a bunch of direct compensations of floating point numbers, + // because we know what we are doing and actually do want the direct + // comparison behaviour. +# pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + + /** + * Calculates the ULP distance between two floating point numbers + * + * The ULP distance of two floating point numbers is the count of + * valid floating point numbers representable between them. + * + * There are some exceptions between how this function counts the + * distance, and the interpretation of the standard as implemented. + * by e.g. `nextafter`. For this function it always holds that: + * * `(x == y) => ulpDistance(x, y) == 0` (so `ulpDistance(-0, 0) == 0`) + * * `ulpDistance(maxFinite, INF) == 1` + * * `ulpDistance(x, -x) == 2 * ulpDistance(x, 0)` + * + * \pre `!isnan( lhs )` + * \pre `!isnan( rhs )` + * \pre floating point numbers are represented in IEEE-754 format + */ + template <typename FP> + uint64_t ulpDistance( FP lhs, FP rhs ) { + assert( std::numeric_limits<FP>::is_iec559 && + "ulpDistance assumes IEEE-754 format for floating point types" ); + assert( !Catch::isnan( lhs ) && + "Distance between NaN and number is not meaningful" ); + assert( !Catch::isnan( rhs ) && + "Distance between NaN and number is not meaningful" ); + + // We want X == Y to imply 0 ULP distance even if X and Y aren't + // bit-equal (-0 and 0), or X - Y != 0 (same sign infinities). + if ( lhs == rhs ) { return 0; } + + // We need a properly typed positive zero for type inference. + static constexpr FP positive_zero{}; + + // We want to ensure that +/- 0 is always represented as positive zero + if ( lhs == positive_zero ) { lhs = positive_zero; } + if ( rhs == positive_zero ) { rhs = positive_zero; } + + // If arguments have different signs, we can handle them by summing + // how far are they from 0 each. + if ( std::signbit( lhs ) != std::signbit( rhs ) ) { + return ulpDistance( std::abs( lhs ), positive_zero ) + + ulpDistance( std::abs( rhs ), positive_zero ); + } + + // When both lhs and rhs are of the same sign, we can just + // read the numbers bitwise as integers, and then subtract them + // (assuming IEEE). + uint64_t lc = Detail::convertToBits( lhs ); + uint64_t rc = Detail::convertToBits( rhs ); + + // The ulp distance between two numbers is symmetric, so to avoid + // dealing with overflows we want the bigger converted number on the lhs + if ( lc < rc ) { + std::swap( lc, rc ); + } + + return lc - rc; + } + +#if defined( __GNUC__ ) || defined( __clang__ ) +# pragma GCC diagnostic pop +#endif + + +} // end namespace Catch + +#endif // CATCH_FLOATING_POINT_HELPERS_HPP_INCLUDED + + +#ifndef CATCH_GETENV_HPP_INCLUDED +#define CATCH_GETENV_HPP_INCLUDED + +namespace Catch { +namespace Detail { + + //! Wrapper over `std::getenv` that compiles on UWP (and always returns nullptr there) + char const* getEnv(char const* varName); + +} +} + +#endif // CATCH_GETENV_HPP_INCLUDED + + +#ifndef CATCH_IS_PERMUTATION_HPP_INCLUDED +#define CATCH_IS_PERMUTATION_HPP_INCLUDED + +#include <algorithm> +#include <iterator> + +namespace Catch { + namespace Detail { + + template <typename ForwardIter, + typename Sentinel, + typename T, + typename Comparator> + constexpr + ForwardIter find_sentinel( ForwardIter start, + Sentinel sentinel, + T const& value, + Comparator cmp ) { + while ( start != sentinel ) { + if ( cmp( *start, value ) ) { break; } + ++start; + } + return start; + } + + template <typename ForwardIter, + typename Sentinel, + typename T, + typename Comparator> + constexpr + std::ptrdiff_t count_sentinel( ForwardIter start, + Sentinel sentinel, + T const& value, + Comparator cmp ) { + std::ptrdiff_t count = 0; + while ( start != sentinel ) { + if ( cmp( *start, value ) ) { ++count; } + ++start; + } + return count; + } + + template <typename ForwardIter, typename Sentinel> + constexpr + std::enable_if_t<!std::is_same<ForwardIter, Sentinel>::value, + std::ptrdiff_t> + sentinel_distance( ForwardIter iter, const Sentinel sentinel ) { + std::ptrdiff_t dist = 0; + while ( iter != sentinel ) { + ++iter; + ++dist; + } + return dist; + } + + template <typename ForwardIter> + constexpr std::ptrdiff_t sentinel_distance( ForwardIter first, + ForwardIter last ) { + return std::distance( first, last ); + } + + template <typename ForwardIter1, + typename Sentinel1, + typename ForwardIter2, + typename Sentinel2, + typename Comparator> + constexpr bool check_element_counts( ForwardIter1 first_1, + const Sentinel1 end_1, + ForwardIter2 first_2, + const Sentinel2 end_2, + Comparator cmp ) { + auto cursor = first_1; + while ( cursor != end_1 ) { + if ( find_sentinel( first_1, cursor, *cursor, cmp ) == + cursor ) { + // we haven't checked this element yet + const auto count_in_range_2 = + count_sentinel( first_2, end_2, *cursor, cmp ); + // Not a single instance in 2nd range, so it cannot be a + // permutation of 1st range + if ( count_in_range_2 == 0 ) { return false; } + + const auto count_in_range_1 = + count_sentinel( cursor, end_1, *cursor, cmp ); + if ( count_in_range_1 != count_in_range_2 ) { + return false; + } + } + + ++cursor; + } + + return true; + } + + template <typename ForwardIter1, + typename Sentinel1, + typename ForwardIter2, + typename Sentinel2, + typename Comparator> + constexpr bool is_permutation( ForwardIter1 first_1, + const Sentinel1 end_1, + ForwardIter2 first_2, + const Sentinel2 end_2, + Comparator cmp ) { + // TODO: no optimization for stronger iterators, because we would also have to constrain on sentinel vs not sentinel types + // TODO: Comparator has to be "both sides", e.g. a == b => b == a + // This skips shared prefix of the two ranges + while (first_1 != end_1 && first_2 != end_2 && cmp(*first_1, *first_2)) { + ++first_1; + ++first_2; + } + + // We need to handle case where at least one of the ranges has no more elements + if (first_1 == end_1 || first_2 == end_2) { + return first_1 == end_1 && first_2 == end_2; + } + + // pair counting is n**2, so we pay linear walk to compare the sizes first + auto dist_1 = sentinel_distance( first_1, end_1 ); + auto dist_2 = sentinel_distance( first_2, end_2 ); + + if (dist_1 != dist_2) { return false; } + + // Since we do not try to handle stronger iterators pair (e.g. + // bidir) optimally, the only thing left to do is to check counts in + // the remaining ranges. + return check_element_counts( first_1, end_1, first_2, end_2, cmp ); + } + + } // namespace Detail +} // namespace Catch + +#endif // CATCH_IS_PERMUTATION_HPP_INCLUDED + + +#ifndef CATCH_ISTREAM_HPP_INCLUDED +#define CATCH_ISTREAM_HPP_INCLUDED + + +#include <iosfwd> +#include <cstddef> +#include <ostream> +#include <string> + +namespace Catch { + + class IStream { + public: + virtual ~IStream(); // = default + virtual std::ostream& stream() = 0; + /** + * Best guess on whether the instance is writing to a console (e.g. via stdout/stderr) + * + * This is useful for e.g. Win32 colour support, because the Win32 + * API manipulates console directly, unlike POSIX escape codes, + * that can be written anywhere. + * + * Due to variety of ways to change where the stdout/stderr is + * _actually_ being written, users should always assume that + * the answer might be wrong. + */ + virtual bool isConsole() const { return false; } + }; + + /** + * Creates a stream wrapper that writes to specific file. + * + * Also recognizes 4 special filenames + * * `-` for stdout + * * `%stdout` for stdout + * * `%stderr` for stderr + * * `%debug` for platform specific debugging output + * + * \throws if passed an unrecognized %-prefixed stream + */ + auto makeStream( std::string const& filename ) -> Detail::unique_ptr<IStream>; + +} + +#endif // CATCH_STREAM_HPP_INCLUDED + + +#ifndef CATCH_JSONWRITER_HPP_INCLUDED +#define CATCH_JSONWRITER_HPP_INCLUDED + + +#include <cstdint> +#include <sstream> + +namespace Catch { + class JsonObjectWriter; + class JsonArrayWriter; + + struct JsonUtils { + static void indent( std::ostream& os, std::uint64_t level ); + static void appendCommaNewline( std::ostream& os, + bool& should_comma, + std::uint64_t level ); + }; + + class JsonValueWriter { + public: + JsonValueWriter( std::ostream& os ); + JsonValueWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonObjectWriter writeObject() &&; + JsonArrayWriter writeArray() &&; + + template <typename T> + void write( T const& value ) && { + writeImpl( value, !std::is_arithmetic<T>::value ); + } + void write( StringRef value ) &&; + void write( bool value ) &&; + + private: + void writeImpl( StringRef value, bool quote ); + + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + template <typename T, + typename = typename std::enable_if_t< + !std::is_convertible<T, StringRef>::value>> + void writeImpl( T const& value, bool quote_value ) { + m_sstream << value; + writeImpl( m_sstream.str(), quote_value ); + } + + std::ostream& m_os; + std::stringstream m_sstream; + std::uint64_t m_indent_level; + }; + + class JsonObjectWriter { + public: + JsonObjectWriter( std::ostream& os ); + JsonObjectWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonObjectWriter( JsonObjectWriter&& source ) noexcept; + JsonObjectWriter& operator=( JsonObjectWriter&& source ) = delete; + + ~JsonObjectWriter(); + + JsonValueWriter write( StringRef key ); + + private: + std::ostream& m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + + class JsonArrayWriter { + public: + JsonArrayWriter( std::ostream& os ); + JsonArrayWriter( std::ostream& os, std::uint64_t indent_level ); + + JsonArrayWriter( JsonArrayWriter&& source ) noexcept; + JsonArrayWriter& operator=( JsonArrayWriter&& source ) = delete; + + ~JsonArrayWriter(); + + JsonObjectWriter writeObject(); + JsonArrayWriter writeArray(); + + template <typename T> + JsonArrayWriter& write( T const& value ) { + return writeImpl( value ); + } + + JsonArrayWriter& write( bool value ); + + private: + template <typename T> + JsonArrayWriter& writeImpl( T const& value ) { + JsonUtils::appendCommaNewline( + m_os, m_should_comma, m_indent_level + 1 ); + JsonValueWriter{ m_os }.write( value ); + + return *this; + } + + std::ostream& m_os; + std::uint64_t m_indent_level; + bool m_should_comma = false; + bool m_active = true; + }; + +} // namespace Catch + +#endif // CATCH_JSONWRITER_HPP_INCLUDED + + +#ifndef CATCH_LEAK_DETECTOR_HPP_INCLUDED +#define CATCH_LEAK_DETECTOR_HPP_INCLUDED + +namespace Catch { + + struct LeakDetector { + LeakDetector(); + ~LeakDetector(); + }; + +} +#endif // CATCH_LEAK_DETECTOR_HPP_INCLUDED + + +#ifndef CATCH_LIST_HPP_INCLUDED +#define CATCH_LIST_HPP_INCLUDED + + +#include <set> +#include <string> + + +namespace Catch { + + class IEventListener; + class Config; + + + struct ReporterDescription { + std::string name, description; + }; + struct ListenerDescription { + StringRef name; + std::string description; + }; + + struct TagInfo { + void add(StringRef spelling); + std::string all() const; + + std::set<StringRef> spellings; + std::size_t count = 0; + }; + + bool list( IEventListener& reporter, Config const& config ); + +} // end namespace Catch + +#endif // CATCH_LIST_HPP_INCLUDED + + +#ifndef CATCH_OUTPUT_REDIRECT_HPP_INCLUDED +#define CATCH_OUTPUT_REDIRECT_HPP_INCLUDED + + +#include <cassert> +#include <string> + +namespace Catch { + + class OutputRedirect { + bool m_redirectActive = false; + virtual void activateImpl() = 0; + virtual void deactivateImpl() = 0; + public: + enum Kind { + //! No redirect (noop implementation) + None, + //! Redirect std::cout/std::cerr/std::clog streams internally + Streams, + //! Redirect the stdout/stderr file descriptors into files + FileDescriptors, + }; + + virtual ~OutputRedirect(); // = default; + + // TODO: Do we want to check that redirect is not active before retrieving the output? + virtual std::string getStdout() = 0; + virtual std::string getStderr() = 0; + virtual void clearBuffers() = 0; + bool isActive() const { return m_redirectActive; } + void activate() { + assert( !m_redirectActive && "redirect is already active" ); + activateImpl(); + m_redirectActive = true; + } + void deactivate() { + assert( m_redirectActive && "redirect is not active" ); + deactivateImpl(); + m_redirectActive = false; + } + }; + + bool isRedirectAvailable( OutputRedirect::Kind kind); + Detail::unique_ptr<OutputRedirect> makeOutputRedirect( bool actual ); + + class RedirectGuard { + OutputRedirect* m_redirect; + bool m_activate; + bool m_previouslyActive; + bool m_moved = false; + + public: + RedirectGuard( bool activate, OutputRedirect& redirectImpl ); + ~RedirectGuard() noexcept( false ); + + RedirectGuard( RedirectGuard const& ) = delete; + RedirectGuard& operator=( RedirectGuard const& ) = delete; + + // C++14 needs move-able guards to return them from functions + RedirectGuard( RedirectGuard&& rhs ) noexcept; + RedirectGuard& operator=( RedirectGuard&& rhs ) noexcept; + }; + + RedirectGuard scopedActivate( OutputRedirect& redirectImpl ); + RedirectGuard scopedDeactivate( OutputRedirect& redirectImpl ); + +} // end namespace Catch + +#endif // CATCH_OUTPUT_REDIRECT_HPP_INCLUDED + + +#ifndef CATCH_PARSE_NUMBERS_HPP_INCLUDED +#define CATCH_PARSE_NUMBERS_HPP_INCLUDED + + +#include <string> + +namespace Catch { + + /** + * Parses unsigned int from the input, using provided base + * + * Effectively a wrapper around std::stoul but with better error checking + * e.g. "-1" is rejected, instead of being parsed as UINT_MAX. + */ + Optional<unsigned int> parseUInt(std::string const& input, int base = 10); +} + +#endif // CATCH_PARSE_NUMBERS_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_REGISTRY_HPP_INCLUDED +#define CATCH_REPORTER_REGISTRY_HPP_INCLUDED + + +#include <map> +#include <string> +#include <vector> + +namespace Catch { + + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + class IReporterFactory; + using IReporterFactoryPtr = Detail::unique_ptr<IReporterFactory>; + struct ReporterConfig; + class EventListenerFactory; + + class ReporterRegistry { + struct ReporterRegistryImpl; + Detail::unique_ptr<ReporterRegistryImpl> m_impl; + + public: + ReporterRegistry(); + ~ReporterRegistry(); // = default; + + IEventListenerPtr create( std::string const& name, + ReporterConfig&& config ) const; + + void registerReporter( std::string const& name, + IReporterFactoryPtr factory ); + + void + registerListener( Detail::unique_ptr<EventListenerFactory> factory ); + + std::map<std::string, + IReporterFactoryPtr, + Detail::CaseInsensitiveLess> const& + getFactories() const; + + std::vector<Detail::unique_ptr<EventListenerFactory>> const& + getListeners() const; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_REGISTRY_HPP_INCLUDED + + +#ifndef CATCH_RUN_CONTEXT_HPP_INCLUDED +#define CATCH_RUN_CONTEXT_HPP_INCLUDED + + + +#ifndef CATCH_TEST_CASE_TRACKER_HPP_INCLUDED +#define CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + + +#include <string> +#include <vector> + +namespace Catch { +namespace TestCaseTracking { + + struct NameAndLocation { + std::string name; + SourceLineInfo location; + + NameAndLocation( std::string&& _name, SourceLineInfo const& _location ); + friend bool operator==(NameAndLocation const& lhs, NameAndLocation const& rhs) { + // This is a very cheap check that should have a very high hit rate. + // If we get to SourceLineInfo::operator==, we will redo it, but the + // cost of repeating is trivial at that point (we will be paying + // multiple strcmp/memcmps at that point). + if ( lhs.location.line != rhs.location.line ) { return false; } + return lhs.name == rhs.name && lhs.location == rhs.location; + } + friend bool operator!=(NameAndLocation const& lhs, + NameAndLocation const& rhs) { + return !( lhs == rhs ); + } + }; + + /** + * This is a variant of `NameAndLocation` that does not own the name string + * + * This avoids extra allocations when trying to locate a tracker by its + * name and location, as long as we make sure that trackers only keep + * around the owning variant. + */ + struct NameAndLocationRef { + StringRef name; + SourceLineInfo location; + + constexpr NameAndLocationRef( StringRef name_, + SourceLineInfo location_ ): + name( name_ ), location( location_ ) {} + + friend bool operator==( NameAndLocation const& lhs, + NameAndLocationRef const& rhs ) { + // This is a very cheap check that should have a very high hit rate. + // If we get to SourceLineInfo::operator==, we will redo it, but the + // cost of repeating is trivial at that point (we will be paying + // multiple strcmp/memcmps at that point). + if ( lhs.location.line != rhs.location.line ) { return false; } + return StringRef( lhs.name ) == rhs.name && + lhs.location == rhs.location; + } + friend bool operator==( NameAndLocationRef const& lhs, + NameAndLocation const& rhs ) { + return rhs == lhs; + } + }; + + class ITracker; + + using ITrackerPtr = Catch::Detail::unique_ptr<ITracker>; + + class ITracker { + NameAndLocation m_nameAndLocation; + + using Children = std::vector<ITrackerPtr>; + + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + + ITracker* m_parent = nullptr; + Children m_children; + CycleState m_runState = NotStarted; + + public: + ITracker( NameAndLocation&& nameAndLoc, ITracker* parent ): + m_nameAndLocation( CATCH_MOVE(nameAndLoc) ), + m_parent( parent ) + {} + + + // static queries + NameAndLocation const& nameAndLocation() const { + return m_nameAndLocation; + } + ITracker* parent() const { + return m_parent; + } + + virtual ~ITracker(); // = default + + + // dynamic queries + + //! Returns true if tracker run to completion (successfully or not) + virtual bool isComplete() const = 0; + //! Returns true if tracker run to completion successfully + bool isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + //! Returns true if tracker has started but hasn't been completed + bool isOpen() const; + //! Returns true iff tracker has started + bool hasStarted() const; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + void markAsNeedingAnotherRun(); + + //! Register a nested ITracker + void addChild( ITrackerPtr&& child ); + /** + * Returns ptr to specific child if register with this tracker. + * + * Returns nullptr if not found. + */ + ITracker* findChild( NameAndLocationRef const& nameAndLocation ); + //! Have any children been added? + bool hasChildren() const { + return !m_children.empty(); + } + + + //! Marks tracker as executing a child, doing se recursively up the tree + void openChild(); + + /** + * Returns true if the instance is a section tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isSectionTracker() const; + /** + * Returns true if the instance is a generator tracker + * + * Subclasses should override to true if they are, replaces RTTI + * for internal debug checks. + */ + virtual bool isGeneratorTracker() const; + }; + + class TrackerContext { + + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; + + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; + + public: + + ITracker& startRun(); + + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void completeCycle(); + + bool completedCycle() const; + ITracker& currentTracker() { return *m_currentTracker; } + void setCurrentTracker( ITracker* tracker ); + }; + + class TrackerBase : public ITracker { + protected: + + TrackerContext& m_ctx; + + public: + TrackerBase( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isComplete() const override; + + void open(); + + void close() override; + void fail() override; + + private: + void moveToParent(); + void moveToThis(); + }; + + class SectionTracker : public TrackerBase { + std::vector<StringRef> m_filters; + // Note that lifetime-wise we piggy back off the name stored in the `ITracker` parent`. + // Currently it allocates owns the name, so this is safe. If it is later refactored + // to not own the name, the name still has to outlive the `ITracker` parent, so + // this should still be safe. + StringRef m_trimmed_name; + public: + SectionTracker( NameAndLocation&& nameAndLocation, TrackerContext& ctx, ITracker* parent ); + + bool isSectionTracker() const override; + + bool isComplete() const override; + + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocationRef const& nameAndLocation ); + + void tryOpen(); + + void addInitialFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<StringRef> const& filters ); + //! Returns filters active in this tracker + std::vector<StringRef> const& getFilters() const { return m_filters; } + //! Returns whitespace-trimmed name of the tracked section + StringRef trimmedName() const; + }; + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; + +} // namespace Catch + +#endif // CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include <string> + +namespace Catch { + + class IGeneratorTracker; + class IConfig; + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + class OutputRedirect; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext final : public IResultCapture { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext( IConfig const* _config, IEventListenerPtr&& reporter ); + + ~RunContext() override; + + Totals runTest(TestCaseHandle const& testCase); + + public: // IResultCapture + + // Assertion handlers + void handleExpr + ( AssertionInfo const& info, + ITransientExpression const& expr, + AssertionReaction& reaction ) override; + void handleMessage + ( AssertionInfo const& info, + ResultWas::OfType resultType, + std::string&& message, + AssertionReaction& reaction ) override; + void handleUnexpectedExceptionNotThrown + ( AssertionInfo const& info, + AssertionReaction& reaction ) override; + void handleUnexpectedInflightException + ( AssertionInfo const& info, + std::string&& message, + AssertionReaction& reaction ) override; + void handleIncomplete + ( AssertionInfo const& info ) override; + void handleNonExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + AssertionReaction &reaction ) override; + + void notifyAssertionStarted( AssertionInfo const& info ) override; + bool sectionStarted( StringRef sectionName, + SourceLineInfo const& sectionLineInfo, + Counts& assertions ) override; + + void sectionEnded( SectionEndInfo&& endInfo ) override; + void sectionEndedEarly( SectionEndInfo&& endInfo ) override; + + IGeneratorTracker* + acquireGeneratorTracker( StringRef generatorName, + SourceLineInfo const& lineInfo ) override; + IGeneratorTracker* createGeneratorTracker( + StringRef generatorName, + SourceLineInfo lineInfo, + Generators::GeneratorBasePtr&& generator ) override; + + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats<> const& stats ) override; + void benchmarkFailed( StringRef error ) override; + + void pushScopedMessage( MessageInfo const& message ) override; + void popScopedMessage( MessageInfo const& message ) override; + + void emplaceUnscopedMessage( MessageBuilder&& builder ) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + public: + // !TBD We need to do this another way! + bool aborting() const; + + private: + + void runCurrentTest(); + void invokeActiveTestCase(); + + void resetAssertionInfo(); + bool testForMissingAssertions( Counts& assertions ); + + void assertionEnded( AssertionResult&& result ); + void reportExpr + ( AssertionInfo const &info, + ResultWas::OfType resultType, + ITransientExpression const *expr, + bool negated ); + + void populateReaction( AssertionReaction& reaction ); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + TestCaseHandle const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker = nullptr; + Optional<AssertionResult> m_lastResult; + + IConfig const* m_config; + Totals m_totals; + IEventListenerPtr m_reporter; + std::vector<MessageInfo> m_messages; + std::vector<ScopedMessage> m_messageScopes; /* Keeps owners of so-called unscoped messages. */ + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + Detail::unique_ptr<OutputRedirect> m_outputRedirect; + FatalConditionHandler m_fatalConditionhandler; + bool m_lastAssertionPassed = false; + bool m_shouldReportUnexpected = true; + bool m_includeSuccessfulResults; + }; + + void seedRng(IConfig const& config); + unsigned int rngSeed(); +} // end namespace Catch + +#endif // CATCH_RUN_CONTEXT_HPP_INCLUDED + + +#ifndef CATCH_SHARDING_HPP_INCLUDED +#define CATCH_SHARDING_HPP_INCLUDED + +#include <cassert> +#include <cmath> +#include <algorithm> + +namespace Catch { + + template<typename Container> + Container createShard(Container const& container, std::size_t const shardCount, std::size_t const shardIndex) { + assert(shardCount > shardIndex); + + if (shardCount == 1) { + return container; + } + + const std::size_t totalTestCount = container.size(); + + const std::size_t shardSize = totalTestCount / shardCount; + const std::size_t leftoverTests = totalTestCount % shardCount; + + const std::size_t startIndex = shardIndex * shardSize + (std::min)(shardIndex, leftoverTests); + const std::size_t endIndex = (shardIndex + 1) * shardSize + (std::min)(shardIndex + 1, leftoverTests); + + auto startIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(startIndex)); + auto endIterator = std::next(container.begin(), static_cast<std::ptrdiff_t>(endIndex)); + + return Container(startIterator, endIterator); + } + +} + +#endif // CATCH_SHARDING_HPP_INCLUDED + + +#ifndef CATCH_SINGLETONS_HPP_INCLUDED +#define CATCH_SINGLETONS_HPP_INCLUDED + +namespace Catch { + + struct ISingleton { + virtual ~ISingleton(); // = default + }; + + + void addSingleton( ISingleton* singleton ); + void cleanupSingletons(); + + + template<typename SingletonImplT, typename InterfaceT = SingletonImplT, typename MutableInterfaceT = InterfaceT> + class Singleton : SingletonImplT, public ISingleton { + + static auto getInternal() -> Singleton* { + static Singleton* s_instance = nullptr; + if( !s_instance ) { + s_instance = new Singleton; + addSingleton( s_instance ); + } + return s_instance; + } + + public: + static auto get() -> InterfaceT const& { + return *getInternal(); + } + static auto getMutable() -> MutableInterfaceT& { + return *getInternal(); + } + }; + +} // namespace Catch + +#endif // CATCH_SINGLETONS_HPP_INCLUDED + + +#ifndef CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED +#define CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + + +#include <vector> +#include <exception> + +namespace Catch { + + class StartupExceptionRegistry { +#if !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector<std::exception_ptr> const& getExceptions() const noexcept; + private: + std::vector<std::exception_ptr> m_exceptions; +#endif + }; + +} // end namespace Catch + +#endif // CATCH_STARTUP_EXCEPTION_REGISTRY_HPP_INCLUDED + + + +#ifndef CATCH_STDSTREAMS_HPP_INCLUDED +#define CATCH_STDSTREAMS_HPP_INCLUDED + +#include <iosfwd> + +namespace Catch { + + std::ostream& cout(); + std::ostream& cerr(); + std::ostream& clog(); + +} // namespace Catch + +#endif + + +#ifndef CATCH_STRING_MANIP_HPP_INCLUDED +#define CATCH_STRING_MANIP_HPP_INCLUDED + + +#include <cstdint> +#include <string> +#include <iosfwd> +#include <vector> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( StringRef s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + char toLower( char c ); + //! Returns a new string without whitespace at the start/end + std::string trim( std::string const& str ); + //! Returns a substring of the original ref without whitespace. Beware lifetimes! + StringRef trim(StringRef ref); + + // !!! Be aware, returns refs into original string - make sure original string outlives them + std::vector<StringRef> splitStringRef( StringRef str, char delimiter ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + /** + * Helper for streaming a "count [maybe-plural-of-label]" human-friendly string + * + * Usage example: + * ```cpp + * std::cout << "Found " << pluralise(count, "error") << '\n'; + * ``` + * + * **Important:** The provided string must outlive the instance + */ + class pluralise { + std::uint64_t m_count; + StringRef m_label; + + public: + constexpr pluralise(std::uint64_t count, StringRef label): + m_count(count), + m_label(label) + {} + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + }; +} + +#endif // CATCH_STRING_MANIP_HPP_INCLUDED + + +#ifndef CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED +#define CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + + +#include <map> +#include <string> + +namespace Catch { + struct SourceLineInfo; + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map<std::string, TagAlias> m_registry; + }; + +} // end namespace Catch + +#endif // CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + + +#ifndef CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED +#define CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED + +#include <cstdint> + +namespace Catch { + + struct TestCaseInfo; + + class TestCaseInfoHasher { + public: + using hash_t = std::uint64_t; + TestCaseInfoHasher( hash_t seed ); + uint32_t operator()( TestCaseInfo const& t ) const; + + private: + hash_t m_seed; + }; + +} // namespace Catch + +#endif /* CATCH_TEST_CASE_INFO_HASHER_HPP_INCLUDED */ + + +#ifndef CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED +#define CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + + +#include <vector> + +namespace Catch { + + class IConfig; + class ITestInvoker; + class TestCaseHandle; + class TestSpec; + + std::vector<TestCaseHandle> sortTests( IConfig const& config, std::vector<TestCaseHandle> const& unsortedTestCases ); + + bool isThrowSafe( TestCaseHandle const& testCase, IConfig const& config ); + + std::vector<TestCaseHandle> filterTests( std::vector<TestCaseHandle> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCaseHandle> const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + void registerTest( Detail::unique_ptr<TestCaseInfo> testInfo, Detail::unique_ptr<ITestInvoker> testInvoker ); + + std::vector<TestCaseInfo*> const& getAllInfos() const override; + std::vector<TestCaseHandle> const& getAllTests() const override; + std::vector<TestCaseHandle> const& getAllTestsSorted( IConfig const& config ) const override; + + ~TestRegistry() override; // = default + + private: + std::vector<Detail::unique_ptr<TestCaseInfo>> m_owned_test_infos; + // Keeps a materialized vector for `getAllInfos`. + // We should get rid of that eventually (see interface note) + std::vector<TestCaseInfo*> m_viewed_test_infos; + + std::vector<Detail::unique_ptr<ITestInvoker>> m_invokers; + std::vector<TestCaseHandle> m_handles; + mutable TestRunOrder m_currentSortOrder = TestRunOrder::Declared; + mutable std::vector<TestCaseHandle> m_sortedFunctions; + }; + + /////////////////////////////////////////////////////////////////////////// + + +} // end namespace Catch + + +#endif // CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + + +#ifndef CATCH_TEST_SPEC_PARSER_HPP_INCLUDED +#define CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + + +#include <vector> +#include <string> + +namespace Catch { + + class ITagAliasRegistry; + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + Mode lastMode = None; + bool m_exclusion = false; + std::size_t m_pos = 0; + std::size_t m_realPatternPos = 0; + std::string m_arg; + std::string m_substring; + std::string m_patternName; + std::vector<std::size_t> m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); + + private: + bool visitChar( char c ); + void startNewMode( Mode mode ); + bool processNoneChar( char c ); + void processNameChar( char c ); + bool processOtherChar( char c ); + void endMode(); + void escape(); + bool isControlChar( char c ) const; + void saveLastMode(); + void revertBackToLastMode(); + void addFilter(); + bool separate(); + + // Handles common preprocessing of the pattern for name/tag patterns + std::string preprocessPattern(); + // Adds the current pattern as a test name + void addNamePattern(); + // Adds the current pattern as a tag + void addTagPattern(); + + inline void addCharToPattern(char c) { + m_substring += c; + m_patternName += c; + m_realPatternPos++; + } + + }; + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif // CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + + +#ifndef CATCH_TEXTFLOW_HPP_INCLUDED +#define CATCH_TEXTFLOW_HPP_INCLUDED + + +#include <cassert> +#include <string> +#include <vector> + +namespace Catch { + namespace TextFlow { + + class Columns; + + /** + * Abstraction for a string with ansi escape sequences that + * automatically skips over escapes when iterating. Only graphical + * escape sequences are considered. + * + * Internal representation: + * An escape sequence looks like \033[39;49m + * We need bidirectional iteration and the unbound length of escape + * sequences poses a problem for operator-- To make this work we'll + * replace the last `m` with a 0xff (this is a codepoint that won't have + * any utf-8 meaning). + */ + class AnsiSkippingString { + std::string m_string; + std::size_t m_size = 0; + + // perform 0xff replacement and calculate m_size + void preprocessString(); + + public: + class const_iterator; + using iterator = const_iterator; + // note: must be u-suffixed or this will cause a "truncation of + // constant value" warning on MSVC + static constexpr char sentinel = static_cast<char>( 0xffu ); + + explicit AnsiSkippingString( std::string const& text ); + explicit AnsiSkippingString( std::string&& text ); + + const_iterator begin() const; + const_iterator end() const; + + size_t size() const { return m_size; } + + std::string substring( const_iterator begin, + const_iterator end ) const; + }; + + class AnsiSkippingString::const_iterator { + friend AnsiSkippingString; + struct EndTag {}; + + const std::string* m_string; + std::string::const_iterator m_it; + + explicit const_iterator( const std::string& string, EndTag ): + m_string( &string ), m_it( string.end() ) {} + + void tryParseAnsiEscapes(); + void advance(); + void unadvance(); + + public: + using difference_type = std::ptrdiff_t; + using value_type = char; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::bidirectional_iterator_tag; + + explicit const_iterator( const std::string& string ): + m_string( &string ), m_it( string.begin() ) { + tryParseAnsiEscapes(); + } + + char operator*() const { return *m_it; } + + const_iterator& operator++() { + advance(); + return *this; + } + const_iterator operator++( int ) { + iterator prev( *this ); + operator++(); + return prev; + } + const_iterator& operator--() { + unadvance(); + return *this; + } + const_iterator operator--( int ) { + iterator prev( *this ); + operator--(); + return prev; + } + + bool operator==( const_iterator const& other ) const { + return m_it == other.m_it; + } + bool operator!=( const_iterator const& other ) const { + return !operator==( other ); + } + bool operator<=( const_iterator const& other ) const { + return m_it <= other.m_it; + } + + const_iterator oneBefore() const { + auto it = *this; + return --it; + } + }; + + /** + * Represents a column of text with specific width and indentation + * + * When written out to a stream, it will perform linebreaking + * of the provided text so that the written lines fit within + * target width. + */ + class Column { + // String to be written out + AnsiSkippingString m_string; + // Width of the column for linebreaking + size_t m_width = CATCH_CONFIG_CONSOLE_WIDTH - 1; + // Indentation of other lines (including first if initial indent is + // unset) + size_t m_indent = 0; + // Indentation of the first line + size_t m_initialIndent = std::string::npos; + + public: + /** + * Iterates "lines" in `Column` and returns them + */ + class const_iterator { + friend Column; + struct EndTag {}; + + Column const& m_column; + // Where does the current line start? + AnsiSkippingString::const_iterator m_lineStart; + // How long should the current line be? + AnsiSkippingString::const_iterator m_lineEnd; + // How far have we checked the string to iterate? + AnsiSkippingString::const_iterator m_parsedTo; + // Should a '-' be appended to the line? + bool m_addHyphen = false; + + const_iterator( Column const& column, EndTag ): + m_column( column ), + m_lineStart( m_column.m_string.end() ), + m_lineEnd( column.m_string.end() ), + m_parsedTo( column.m_string.end() ) {} + + // Calculates the length of the current line + void calcLength(); + + // Returns current indentation width + size_t indentSize() const; + + // Creates an indented and (optionally) suffixed string from + // current iterator position, indentation and length. + std::string addIndentAndSuffix( + AnsiSkippingString::const_iterator start, + AnsiSkippingString::const_iterator end ) const; + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + explicit const_iterator( Column const& column ); + + std::string operator*() const; + + const_iterator& operator++(); + const_iterator operator++( int ); + + bool operator==( const_iterator const& other ) const { + return m_lineStart == other.m_lineStart && + &m_column == &other.m_column; + } + bool operator!=( const_iterator const& other ) const { + return !operator==( other ); + } + }; + using iterator = const_iterator; + + explicit Column( std::string const& text ): m_string( text ) {} + explicit Column( std::string&& text ): + m_string( CATCH_MOVE( text ) ) {} + + Column& width( size_t newWidth ) & { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; + } + Column&& width( size_t newWidth ) && { + assert( newWidth > 0 ); + m_width = newWidth; + return CATCH_MOVE( *this ); + } + Column& indent( size_t newIndent ) & { + m_indent = newIndent; + return *this; + } + Column&& indent( size_t newIndent ) && { + m_indent = newIndent; + return CATCH_MOVE( *this ); + } + Column& initialIndent( size_t newIndent ) & { + m_initialIndent = newIndent; + return *this; + } + Column&& initialIndent( size_t newIndent ) && { + m_initialIndent = newIndent; + return CATCH_MOVE( *this ); + } + + size_t width() const { return m_width; } + const_iterator begin() const { return const_iterator( *this ); } + const_iterator end() const { + return { *this, const_iterator::EndTag{} }; + } + + friend std::ostream& operator<<( std::ostream& os, + Column const& col ); + + friend Columns operator+( Column const& lhs, Column const& rhs ); + friend Columns operator+( Column&& lhs, Column&& rhs ); + }; + + //! Creates a column that serves as an empty space of specific width + Column Spacer( size_t spaceWidth ); + + class Columns { + std::vector<Column> m_columns; + + public: + class iterator { + friend Columns; + struct EndTag {}; + + std::vector<Column> const& m_columns; + std::vector<Column::const_iterator> m_iterators; + size_t m_activeIterators; + + iterator( Columns const& columns, EndTag ); + + public: + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::forward_iterator_tag; + + explicit iterator( Columns const& columns ); + + auto operator==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; + } + auto operator!=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; + } + std::string operator*() const; + iterator& operator++(); + iterator operator++( int ); + }; + using const_iterator = iterator; + + iterator begin() const { return iterator( *this ); } + iterator end() const { return { *this, iterator::EndTag() }; } + + friend Columns& operator+=( Columns& lhs, Column const& rhs ); + friend Columns& operator+=( Columns& lhs, Column&& rhs ); + friend Columns operator+( Columns const& lhs, Column const& rhs ); + friend Columns operator+( Columns&& lhs, Column&& rhs ); + + friend std::ostream& operator<<( std::ostream& os, + Columns const& cols ); + }; + + } // namespace TextFlow +} // namespace Catch +#endif // CATCH_TEXTFLOW_HPP_INCLUDED + + +#ifndef CATCH_TO_STRING_HPP_INCLUDED +#define CATCH_TO_STRING_HPP_INCLUDED + +#include <string> + + +namespace Catch { + template <typename T> + std::string to_string(T const& t) { +#if defined(CATCH_CONFIG_CPP11_TO_STRING) + return std::to_string(t); +#else + ReusableStringStream rss; + rss << t; + return rss.str(); +#endif + } +} // end namespace Catch + +#endif // CATCH_TO_STRING_HPP_INCLUDED + + +#ifndef CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED +#define CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + +namespace Catch { + bool uncaught_exceptions(); +} // end namespace Catch + +#endif // CATCH_UNCAUGHT_EXCEPTIONS_HPP_INCLUDED + + +#ifndef CATCH_XMLWRITER_HPP_INCLUDED +#define CATCH_XMLWRITER_HPP_INCLUDED + + +#include <iosfwd> +#include <vector> +#include <cstdint> + +namespace Catch { + enum class XmlFormatting : std::uint8_t { + None = 0x00, + Indent = 0x01, + Newline = 0x02, + }; + + constexpr XmlFormatting operator|( XmlFormatting lhs, XmlFormatting rhs ) { + return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) | + static_cast<std::uint8_t>( rhs ) ); + } + + constexpr XmlFormatting operator&( XmlFormatting lhs, XmlFormatting rhs ) { + return static_cast<XmlFormatting>( static_cast<std::uint8_t>( lhs ) & + static_cast<std::uint8_t>( rhs ) ); + } + + + /** + * Helper for XML-encoding text (escaping angle brackets, quotes, etc) + * + * Note: doesn't take ownership of passed strings, and thus the + * encoded string must outlive the encoding instance. + */ + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + constexpr XmlEncode( StringRef str, ForWhat forWhat = ForTextNodes ): + m_str( str ), m_forWhat( forWhat ) {} + + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + StringRef m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer, XmlFormatting fmt ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& + writeText( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); + + ScopedElement& writeAttribute( StringRef name, + StringRef attribute ); + template <typename T, + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + typename = typename std::enable_if_t< + !std::is_convertible<T, StringRef>::value>> + ScopedElement& writeAttribute( StringRef name, + T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + XmlWriter* m_writer = nullptr; + XmlFormatting m_fmt; + }; + + XmlWriter( std::ostream& os ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); + + //! The attribute content is XML-encoded + XmlWriter& writeAttribute( StringRef name, StringRef attribute ); + + //! Writes the attribute as "true/false" + XmlWriter& writeAttribute( StringRef name, bool attribute ); + + //! The attribute content is XML-encoded + XmlWriter& writeAttribute( StringRef name, char const* attribute ); + + //! The attribute value must provide op<<(ostream&, T). The resulting + //! serialization is XML-encoded + template <typename T, + // Without this SFINAE, this overload is a better match + // for `std::string`, `char const*`, `char const[N]` args. + // While it would still work, it would cause code bloat + // and multiple iteration over the strings + typename = typename std::enable_if_t< + !std::is_convertible<T, StringRef>::value>> + XmlWriter& writeAttribute( StringRef name, T const& attribute ) { + ReusableStringStream rss; + rss << attribute; + return writeAttribute( name, rss.str() ); + } + + //! Writes escaped `text` in a element + XmlWriter& writeText( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); + + //! Writes XML comment as "<!-- text -->" + XmlWriter& writeComment( StringRef text, + XmlFormatting fmt = XmlFormatting::Newline | + XmlFormatting::Indent ); + + void writeStylesheetRef( StringRef url ); + + void ensureTagClosed(); + + private: + + void applyFormatting(XmlFormatting fmt); + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + }; + +} + +#endif // CATCH_XMLWRITER_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2's Matcher support. It includes + * **all** of Catch2 headers related to matchers. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of increased compilation times. + * + * When a new header is added to either the `matchers` folder, or to + * the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_MATCHERS_ALL_HPP_INCLUDED +#define CATCH_MATCHERS_ALL_HPP_INCLUDED + + + +#ifndef CATCH_MATCHERS_HPP_INCLUDED +#define CATCH_MATCHERS_HPP_INCLUDED + + + +#ifndef CATCH_MATCHERS_IMPL_HPP_INCLUDED +#define CATCH_MATCHERS_IMPL_HPP_INCLUDED + + +#include <string> + +namespace Catch { + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wsign-compare" +# pragma clang diagnostic ignored "-Wnon-virtual-dtor" +#elif defined __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wsign-compare" +# pragma GCC diagnostic ignored "-Wnon-virtual-dtor" +#endif + + template<typename ArgT, typename MatcherT> + class MatchExpr : public ITransientExpression { + ArgT && m_arg; + MatcherT const& m_matcher; + public: + constexpr MatchExpr( ArgT && arg, MatcherT const& matcher ) + : ITransientExpression{ true, matcher.match( arg ) }, // not forwarding arg here on purpose + m_arg( CATCH_FORWARD(arg) ), + m_matcher( matcher ) + {} + + void streamReconstructedExpression( std::ostream& os ) const override { + os << Catch::Detail::stringify( m_arg ) + << ' ' + << m_matcher.toString(); + } + }; + +#ifdef __clang__ +# pragma clang diagnostic pop +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + + namespace Matchers { + template <typename ArgT> + class MatcherBase; + } + + using StringMatcher = Matchers::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher ); + + template<typename ArgT, typename MatcherT> + constexpr MatchExpr<ArgT, MatcherT> + makeMatchExpr( ArgT&& arg, MatcherT const& matcher ) { + return MatchExpr<ArgT, MatcherT>( CATCH_FORWARD(arg), matcher ); + } + +} // namespace Catch + + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( arg, matcher ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + catchAssertionHandler.complete(); \ + } while( false ) + + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName##_catch_sr, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_USELESS_CAST_WARNINGS \ + static_cast<void>(__VA_ARGS__ ); \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION \ + catchAssertionHandler.handleUnexpectedExceptionNotThrown(); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handleExpr( Catch::makeMatchExpr( ex, matcher ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.handleUnexpectedInflightException(); \ + } \ + else \ + catchAssertionHandler.handleThrowingCallSkipped(); \ + catchAssertionHandler.complete(); \ + } while( false ) + + +#endif // CATCH_MATCHERS_IMPL_HPP_INCLUDED + +#include <string> +#include <vector> + +namespace Catch { +namespace Matchers { + + class MatcherUntypedBase { + public: + MatcherUntypedBase() = default; + + MatcherUntypedBase(MatcherUntypedBase const&) = default; + MatcherUntypedBase(MatcherUntypedBase&&) = default; + + MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete; + MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete; + + std::string toString() const; + + protected: + virtual ~MatcherUntypedBase(); // = default; + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; + + + template<typename T> + class MatcherBase : public MatcherUntypedBase { + public: + virtual bool match( T const& arg ) const = 0; + }; + + namespace Detail { + + template<typename ArgT> + class MatchAllOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + + public: + MatchAllOf() = default; + MatchAllOf(MatchAllOf const&) = delete; + MatchAllOf& operator=(MatchAllOf const&) = delete; + MatchAllOf(MatchAllOf&&) = default; + MatchAllOf& operator=(MatchAllOf&&) = default; + + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) + return false; + } + return true; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAllOf operator&& (MatchAllOf&& lhs, MatcherBase<ArgT> const& rhs) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAllOf operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf&& rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAllOf<ArgT> operator&& (MatchAllOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAllOf<ArgT> operator&& (MatcherBase<ArgT> const& lhs, MatchAllOf<ArgT> const& rhs) = delete; + + template<typename ArgT> + class MatchAnyOf final : public MatcherBase<ArgT> { + std::vector<MatcherBase<ArgT> const*> m_matchers; + public: + MatchAnyOf() = default; + MatchAnyOf(MatchAnyOf const&) = delete; + MatchAnyOf& operator=(MatchAnyOf const&) = delete; + MatchAnyOf(MatchAnyOf&&) = default; + MatchAnyOf& operator=(MatchAnyOf&&) = default; + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) + return true; + } + return false; + } + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); + } + description += " )"; + return description; + } + + friend MatchAnyOf operator|| (MatchAnyOf&& lhs, MatcherBase<ArgT> const& rhs) { + lhs.m_matchers.push_back(&rhs); + return CATCH_MOVE(lhs); + } + friend MatchAnyOf operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf&& rhs) { + rhs.m_matchers.insert(rhs.m_matchers.begin(), &lhs); + return CATCH_MOVE(rhs); + } + }; + + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAnyOf<ArgT> operator|| (MatchAnyOf<ArgT> const& lhs, MatcherBase<ArgT> const& rhs) = delete; + //! lvalue overload is intentionally deleted, users should + //! not be trying to compose stored composition matchers + template<typename ArgT> + MatchAnyOf<ArgT> operator|| (MatcherBase<ArgT> const& lhs, MatchAnyOf<ArgT> const& rhs) = delete; + + template<typename ArgT> + class MatchNotOf final : public MatcherBase<ArgT> { + MatcherBase<ArgT> const& m_underlyingMatcher; + + public: + explicit MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ): + m_underlyingMatcher( underlyingMatcher ) + {} + + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } + + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + }; + + } // namespace Detail + + template <typename T> + Detail::MatchAllOf<T> operator&& (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { + return Detail::MatchAllOf<T>{} && lhs && rhs; + } + template <typename T> + Detail::MatchAnyOf<T> operator|| (MatcherBase<T> const& lhs, MatcherBase<T> const& rhs) { + return Detail::MatchAnyOf<T>{} || lhs || rhs; + } + + template <typename T> + Detail::MatchNotOf<T> operator! (MatcherBase<T> const& matcher) { + return Detail::MatchNotOf<T>{ matcher }; + } + + +} // namespace Matchers +} // namespace Catch + + +#if defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) + #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) + + #define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + + #define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + #define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + +#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) + #define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) + #define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + #define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE) + + #define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) + #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) + + #define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) + + #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + #define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) + +#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE) + + #define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) + #define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CHECK_THROWS_WITH( expr, matcher ) (void)(0) + #define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) + + #define CHECK_THAT( arg, matcher ) (void)(0) + #define REQUIRE_THAT( arg, matcher ) (void)(0) + +#endif // end of user facing macro declarations + +#endif // CATCH_MATCHERS_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + + + +#ifndef CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED +#define CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED + + +#include <array> +#include <algorithm> +#include <string> +#include <type_traits> + +namespace Catch { +namespace Matchers { + class MatcherGenericBase : public MatcherUntypedBase { + public: + MatcherGenericBase() = default; + ~MatcherGenericBase() override; // = default; + + MatcherGenericBase(MatcherGenericBase const&) = default; + MatcherGenericBase(MatcherGenericBase&&) = default; + + MatcherGenericBase& operator=(MatcherGenericBase const&) = delete; + MatcherGenericBase& operator=(MatcherGenericBase&&) = delete; + }; + + + namespace Detail { + template<std::size_t N, std::size_t M> + std::array<void const*, N + M> array_cat(std::array<void const*, N> && lhs, std::array<void const*, M> && rhs) { + std::array<void const*, N + M> arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + std::copy_n(rhs.begin(), M, arr.begin() + N); + return arr; + } + + template<std::size_t N> + std::array<void const*, N+1> array_cat(std::array<void const*, N> && lhs, void const* rhs) { + std::array<void const*, N+1> arr{}; + std::copy_n(lhs.begin(), N, arr.begin()); + arr[N] = rhs; + return arr; + } + + template<std::size_t N> + std::array<void const*, N+1> array_cat(void const* lhs, std::array<void const*, N> && rhs) { + std::array<void const*, N + 1> arr{ {lhs} }; + std::copy_n(rhs.begin(), N, arr.begin() + 1); + return arr; + } + + template<typename T> + using is_generic_matcher = std::is_base_of< + Catch::Matchers::MatcherGenericBase, + std::remove_cv_t<std::remove_reference_t<T>> + >; + + template<typename... Ts> + using are_generic_matchers = Catch::Detail::conjunction<is_generic_matcher<Ts>...>; + + template<typename T> + using is_matcher = std::is_base_of< + Catch::Matchers::MatcherUntypedBase, + std::remove_cv_t<std::remove_reference_t<T>> + >; + + + template<std::size_t N, typename Arg> + bool match_all_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) { + return true; + } + + template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices> + bool match_all_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) { + return static_cast<T const*>(matchers[Idx])->match(arg) && match_all_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{}); + } + + + template<std::size_t N, typename Arg> + bool match_any_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) { + return false; + } + + template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices> + bool match_any_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) { + return static_cast<T const*>(matchers[Idx])->match(arg) || match_any_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{}); + } + + std::string describe_multi_matcher(StringRef combine, std::string const* descriptions_begin, std::string const* descriptions_end); + + template<typename... MatcherTs, std::size_t... Idx> + std::string describe_multi_matcher(StringRef combine, std::array<void const*, sizeof...(MatcherTs)> const& matchers, std::index_sequence<Idx...>) { + std::array<std::string, sizeof...(MatcherTs)> descriptions {{ + static_cast<MatcherTs const*>(matchers[Idx])->toString()... + }}; + + return describe_multi_matcher(combine, descriptions.data(), descriptions.data() + descriptions.size()); + } + + + template<typename... MatcherTs> + class MatchAllOfGeneric final : public MatcherGenericBase { + public: + MatchAllOfGeneric(MatchAllOfGeneric const&) = delete; + MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete; + MatchAllOfGeneric(MatchAllOfGeneric&&) = default; + MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default; + + MatchAllOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return match_all_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + std::string describe() const override { + return describe_multi_matcher<MatcherTs...>(" and "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; + + + //! Avoids type nesting for `GenericAllOf && GenericAllOf` case + template<typename... MatchersRHS> + friend + MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && ( + MatchAllOfGeneric<MatcherTs...>&& lhs, + MatchAllOfGeneric<MatchersRHS...>&& rhs) { + return MatchAllOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAllOf && some matcher` case + template<typename MatcherRHS> + friend std::enable_if_t<is_matcher<MatcherRHS>::value, + MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && ( + MatchAllOfGeneric<MatcherTs...>&& lhs, + MatcherRHS const& rhs) { + return MatchAllOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(&rhs))}; + } + + //! Avoids type nesting for `some matcher && GenericAllOf` case + template<typename MatcherLHS> + friend std::enable_if_t<is_matcher<MatcherLHS>::value, + MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && ( + MatcherLHS const& lhs, + MatchAllOfGeneric<MatcherTs...>&& rhs) { + return MatchAllOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + + template<typename... MatcherTs> + class MatchAnyOfGeneric final : public MatcherGenericBase { + public: + MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete; + MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete; + MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default; + MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default; + + MatchAnyOfGeneric(MatcherTs const&... matchers) : m_matchers{ {std::addressof(matchers)...} } {} + explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return match_any_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + std::string describe() const override { + return describe_multi_matcher<MatcherTs...>(" or "_sr, m_matchers, std::index_sequence_for<MatcherTs...>{}); + } + + + // Has to be public to enable the concatenating operators + // below, because they are not friend of the RHS, only LHS, + // and thus cannot access private fields of RHS + std::array<void const*, sizeof...( MatcherTs )> m_matchers; + + //! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case + template<typename... MatchersRHS> + friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || ( + MatchAnyOfGeneric<MatcherTs...>&& lhs, + MatchAnyOfGeneric<MatchersRHS...>&& rhs) { + return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))}; + } + + //! Avoids type nesting for `GenericAnyOf || some matcher` case + template<typename MatcherRHS> + friend std::enable_if_t<is_matcher<MatcherRHS>::value, + MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || ( + MatchAnyOfGeneric<MatcherTs...>&& lhs, + MatcherRHS const& rhs) { + return MatchAnyOfGeneric<MatcherTs..., MatcherRHS>{array_cat(CATCH_MOVE(lhs.m_matchers), static_cast<void const*>(std::addressof(rhs)))}; + } + + //! Avoids type nesting for `some matcher || GenericAnyOf` case + template<typename MatcherLHS> + friend std::enable_if_t<is_matcher<MatcherLHS>::value, + MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || ( + MatcherLHS const& lhs, + MatchAnyOfGeneric<MatcherTs...>&& rhs) { + return MatchAnyOfGeneric<MatcherLHS, MatcherTs...>{array_cat(static_cast<void const*>(std::addressof(lhs)), CATCH_MOVE(rhs.m_matchers))}; + } + }; + + + template<typename MatcherT> + class MatchNotOfGeneric final : public MatcherGenericBase { + MatcherT const& m_matcher; + + public: + MatchNotOfGeneric(MatchNotOfGeneric const&) = delete; + MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete; + MatchNotOfGeneric(MatchNotOfGeneric&&) = default; + MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default; + + explicit MatchNotOfGeneric(MatcherT const& matcher) : m_matcher{matcher} {} + + template<typename Arg> + bool match(Arg&& arg) const { + return !m_matcher.match(arg); + } + + std::string describe() const override { + return "not " + m_matcher.toString(); + } + + //! Negating negation can just unwrap and return underlying matcher + friend MatcherT const& operator ! (MatchNotOfGeneric<MatcherT> const& matcher) { + return matcher.m_matcher; + } + }; + } // namespace Detail + + + // compose only generic matchers + template<typename MatcherLHS, typename MatcherRHS> + std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>> + operator && (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + template<typename MatcherLHS, typename MatcherRHS> + std::enable_if_t<Detail::are_generic_matchers<MatcherLHS, MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>> + operator || (MatcherLHS const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + //! Wrap provided generic matcher in generic negator + template<typename MatcherT> + std::enable_if_t<Detail::is_generic_matcher<MatcherT>::value, Detail::MatchNotOfGeneric<MatcherT>> + operator ! (MatcherT const& matcher) { + return Detail::MatchNotOfGeneric<MatcherT>{matcher}; + } + + + // compose mixed generic and non-generic matchers + template<typename MatcherLHS, typename ArgRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAllOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> + operator && (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { + return { lhs, rhs }; + } + + template<typename ArgLHS, typename MatcherRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAllOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> + operator && (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + + template<typename MatcherLHS, typename ArgRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherLHS>::value, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherBase<ArgRHS>>> + operator || (MatcherLHS const& lhs, MatcherBase<ArgRHS> const& rhs) { + return { lhs, rhs }; + } + + template<typename ArgLHS, typename MatcherRHS> + std::enable_if_t<Detail::is_generic_matcher<MatcherRHS>::value, Detail::MatchAnyOfGeneric<MatcherBase<ArgLHS>, MatcherRHS>> + operator || (MatcherBase<ArgLHS> const& lhs, MatcherRHS const& rhs) { + return { lhs, rhs }; + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_TEMPLATED_HPP_INCLUDED + +namespace Catch { + namespace Matchers { + + class IsEmptyMatcher final : public MatcherGenericBase { + public: + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::empty; +#else + using std::empty; +#endif + return empty(rng); + } + + std::string describe() const override; + }; + + class HasSizeMatcher final : public MatcherGenericBase { + std::size_t m_target_size; + public: + explicit HasSizeMatcher(std::size_t target_size): + m_target_size(target_size) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return size(rng) == m_target_size; + } + + std::string describe() const override; + }; + + template <typename Matcher> + class SizeMatchesMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + explicit SizeMatchesMatcher(Matcher m): + m_matcher(CATCH_MOVE(m)) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { +#if defined(CATCH_CONFIG_POLYFILL_NONMEMBER_CONTAINER_ACCESS) + using Catch::Detail::size; +#else + using std::size; +#endif + return m_matcher.match(size(rng)); + } + + std::string describe() const override { + return "size matches " + m_matcher.describe(); + } + }; + + + //! Creates a matcher that accepts empty ranges/containers + IsEmptyMatcher IsEmpty(); + //! Creates a matcher that accepts ranges/containers with specific size + HasSizeMatcher SizeIs(std::size_t sz); + template <typename Matcher> + std::enable_if_t<Detail::is_matcher<Matcher>::value, + SizeMatchesMatcher<Matcher>> SizeIs(Matcher&& m) { + return SizeMatchesMatcher<Matcher>{CATCH_FORWARD(m)}; + } + + } // end namespace Matchers +} // end namespace Catch + +#endif // CATCH_MATCHERS_CONTAINER_PROPERTIES_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_CONTAINS_HPP_INCLUDED +#define CATCH_MATCHERS_CONTAINS_HPP_INCLUDED + + +#include <algorithm> +#include <functional> + +namespace Catch { + namespace Matchers { + //! Matcher for checking that an element in range is equal to specific element + template <typename T, typename Equality> + class ContainsElementMatcher final : public MatcherGenericBase { + T m_desired; + Equality m_eq; + public: + template <typename T2, typename Equality2> + ContainsElementMatcher(T2&& target, Equality2&& predicate): + m_desired(CATCH_FORWARD(target)), + m_eq(CATCH_FORWARD(predicate)) + {} + + std::string describe() const override { + return "contains element " + Catch::Detail::stringify(m_desired); + } + + template <typename RangeLike> + bool match( RangeLike&& rng ) const { + for ( auto&& elem : rng ) { + if ( m_eq( elem, m_desired ) ) { return true; } + } + return false; + } + }; + + //! Meta-matcher for checking that an element in a range matches a specific matcher + template <typename Matcher> + class ContainsMatcherMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + // Note that we do a copy+move to avoid having to SFINAE this + // constructor (and also avoid some perfect forwarding failure + // cases) + ContainsMatcherMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "contains element matching " + m_matcher.describe(); + } + }; + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `std::equal_to` to do the comparison + */ + template <typename T> + std::enable_if_t<!Detail::is_matcher<T>::value, + ContainsElementMatcher<T, std::equal_to<>>> Contains(T&& elem) { + return { CATCH_FORWARD(elem), std::equal_to<>{} }; + } + + //! Creates a matcher that checks whether a range contains element matching a matcher + template <typename Matcher> + std::enable_if_t<Detail::is_matcher<Matcher>::value, + ContainsMatcherMatcher<Matcher>> Contains(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + /** + * Creates a matcher that checks whether a range contains a specific element. + * + * Uses `eq` to do the comparisons, the element is provided on the rhs + */ + template <typename T, typename Equality> + ContainsElementMatcher<T, Equality> Contains(T&& elem, Equality&& eq) { + return { CATCH_FORWARD(elem), CATCH_FORWARD(eq) }; + } + + } +} + +#endif // CATCH_MATCHERS_CONTAINS_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED +#define CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED + + +namespace Catch { +namespace Matchers { + +class ExceptionMessageMatcher final : public MatcherBase<std::exception> { + std::string m_message; +public: + + ExceptionMessageMatcher(std::string const& message): + m_message(message) + {} + + bool match(std::exception const& ex) const override; + + std::string describe() const override; +}; + +//! Creates a matcher that checks whether a std derived exception has the provided message +ExceptionMessageMatcher Message(std::string const& message); + +template <typename StringMatcherType> +class ExceptionMessageMatchesMatcher final + : public MatcherBase<std::exception> { + StringMatcherType m_matcher; + +public: + ExceptionMessageMatchesMatcher( StringMatcherType matcher ): + m_matcher( CATCH_MOVE( matcher ) ) {} + + bool match( std::exception const& ex ) const override { + return m_matcher.match( ex.what() ); + } + + std::string describe() const override { + return " matches \"" + m_matcher.describe() + '"'; + } +}; + +//! Creates a matcher that checks whether a message from an std derived +//! exception matches a provided matcher +template <typename StringMatcherType> +ExceptionMessageMatchesMatcher<StringMatcherType> +MessageMatches( StringMatcherType&& matcher ) { + return { CATCH_FORWARD( matcher ) }; +} + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_EXCEPTION_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED +#define CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED + + +namespace Catch { +namespace Matchers { + + namespace Detail { + enum class FloatingPointKind : uint8_t; + } + + class WithinAbsMatcher final : public MatcherBase<double> { + public: + WithinAbsMatcher(double target, double margin); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_margin; + }; + + //! Creates a matcher that accepts numbers within certain range of target + WithinAbsMatcher WithinAbs( double target, double margin ); + + + + class WithinUlpsMatcher final : public MatcherBase<double> { + public: + WithinUlpsMatcher( double target, + uint64_t ulps, + Detail::FloatingPointKind baseType ); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + uint64_t m_ulps; + Detail::FloatingPointKind m_type; + }; + + //! Creates a matcher that accepts doubles within certain ULP range of target + WithinUlpsMatcher WithinULP(double target, uint64_t maxUlpDiff); + //! Creates a matcher that accepts floats within certain ULP range of target + WithinUlpsMatcher WithinULP(float target, uint64_t maxUlpDiff); + + + + // Given IEEE-754 format for floats and doubles, we can assume + // that float -> double promotion is lossless. Given this, we can + // assume that if we do the standard relative comparison of + // |lhs - rhs| <= epsilon * max(fabs(lhs), fabs(rhs)), then we get + // the same result if we do this for floats, as if we do this for + // doubles that were promoted from floats. + class WithinRelMatcher final : public MatcherBase<double> { + public: + WithinRelMatcher( double target, double epsilon ); + bool match(double const& matchee) const override; + std::string describe() const override; + private: + double m_target; + double m_epsilon; + }; + + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(double target, double eps); + //! Creates a matcher that accepts doubles within 100*DBL_EPS relative range of target + WithinRelMatcher WithinRel(double target); + //! Creates a matcher that accepts doubles within certain relative range of target + WithinRelMatcher WithinRel(float target, float eps); + //! Creates a matcher that accepts floats within 100*FLT_EPS relative range of target + WithinRelMatcher WithinRel(float target); + + + + class IsNaNMatcher final : public MatcherBase<double> { + public: + IsNaNMatcher() = default; + bool match( double const& matchee ) const override; + std::string describe() const override; + }; + + IsNaNMatcher IsNaN(); + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_FLOATING_POINT_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_PREDICATE_HPP_INCLUDED +#define CATCH_MATCHERS_PREDICATE_HPP_INCLUDED + + +#include <string> + +namespace Catch { +namespace Matchers { + +namespace Detail { + std::string finalizeDescription(const std::string& desc); +} // namespace Detail + +template <typename T, typename Predicate> +class PredicateMatcher final : public MatcherBase<T> { + Predicate m_predicate; + std::string m_description; +public: + + PredicateMatcher(Predicate&& elem, std::string const& descr) + :m_predicate(CATCH_FORWARD(elem)), + m_description(Detail::finalizeDescription(descr)) + {} + + bool match( T const& item ) const override { + return m_predicate(item); + } + + std::string describe() const override { + return m_description; + } +}; + + /** + * Creates a matcher that calls delegates `match` to the provided predicate. + * + * The user has to explicitly specify the argument type to the matcher + */ + template<typename T, typename Pred> + PredicateMatcher<T, Pred> Predicate(Pred&& predicate, std::string const& description = "") { + static_assert(is_callable<Pred(T)>::value, "Predicate not callable with argument T"); + static_assert(std::is_same<bool, FunctionReturnType<Pred, T>>::value, "Predicate does not return bool"); + return PredicateMatcher<T, Pred>(CATCH_FORWARD(predicate), description); + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_PREDICATE_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED +#define CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + + +namespace Catch { + namespace Matchers { + // Matcher for checking that all elements in range matches a given matcher. + template <typename Matcher> + class AllMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AllMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "all match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (!m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range matches a given matcher. + template <typename Matcher> + class NoneMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + NoneMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "none match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that at least one element in range matches a given matcher. + template <typename Matcher> + class AnyMatchMatcher final : public MatcherGenericBase { + Matcher m_matcher; + public: + AnyMatchMatcher(Matcher matcher): + m_matcher(CATCH_MOVE(matcher)) + {} + + std::string describe() const override { + return "any match " + m_matcher.describe(); + } + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (m_matcher.match(elem)) { + return true; + } + } + return false; + } + }; + + // Matcher for checking that all elements in range are true. + class AllTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (!elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that no element in range is true. + class NoneTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (elem) { + return false; + } + } + return true; + } + }; + + // Matcher for checking that any element in range is true. + class AnyTrueMatcher final : public MatcherGenericBase { + public: + std::string describe() const override; + + template <typename RangeLike> + bool match(RangeLike&& rng) const { + for (auto&& elem : rng) { + if (elem) { + return true; + } + } + return false; + } + }; + + // Creates a matcher that checks whether all elements in a range match a matcher + template <typename Matcher> + AllMatchMatcher<Matcher> AllMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether no element in a range matches a matcher. + template <typename Matcher> + NoneMatchMatcher<Matcher> NoneMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether any element in a range matches a matcher. + template <typename Matcher> + AnyMatchMatcher<Matcher> AnyMatch(Matcher&& matcher) { + return { CATCH_FORWARD(matcher) }; + } + + // Creates a matcher that checks whether all elements in a range are true + AllTrueMatcher AllTrue(); + + // Creates a matcher that checks whether no element in a range is true + NoneTrueMatcher NoneTrue(); + + // Creates a matcher that checks whether any element in a range is true + AnyTrueMatcher AnyTrue(); + } +} + +#endif // CATCH_MATCHERS_QUANTIFIERS_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED +#define CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED + + +#include <algorithm> +#include <utility> + +namespace Catch { + namespace Matchers { + + /** + * Matcher for checking that an element contains the same + * elements in the same order + */ + template <typename TargetRangeLike, typename Equality> + class RangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template <typename TargetRangeLike2, typename Equality2> + constexpr + RangeEqualsMatcher( TargetRangeLike2&& range, + Equality2&& predicate ): + m_desired( CATCH_FORWARD( range ) ), + m_predicate( CATCH_FORWARD( predicate ) ) {} + + template <typename RangeLike> + constexpr + bool match( RangeLike&& rng ) const { + auto rng_start = begin( rng ); + const auto rng_end = end( rng ); + auto target_start = begin( m_desired ); + const auto target_end = end( m_desired ); + + while (rng_start != rng_end && target_start != target_end) { + if (!m_predicate(*rng_start, *target_start)) { + return false; + } + ++rng_start; + ++target_start; + } + return rng_start == rng_end && target_start == target_end; + } + + std::string describe() const override { + return "elements are " + Catch::Detail::stringify( m_desired ); + } + }; + + /** + * Matcher for checking that an element contains the same + * elements (but not necessarily in the same order) + */ + template <typename TargetRangeLike, typename Equality> + class UnorderedRangeEqualsMatcher final : public MatcherGenericBase { + TargetRangeLike m_desired; + Equality m_predicate; + + public: + template <typename TargetRangeLike2, typename Equality2> + constexpr + UnorderedRangeEqualsMatcher( TargetRangeLike2&& range, + Equality2&& predicate ): + m_desired( CATCH_FORWARD( range ) ), + m_predicate( CATCH_FORWARD( predicate ) ) {} + + template <typename RangeLike> + constexpr + bool match( RangeLike&& rng ) const { + using std::begin; + using std::end; + return Catch::Detail::is_permutation( begin( m_desired ), + end( m_desired ), + begin( rng ), + end( rng ), + m_predicate ); + } + + std::string describe() const override { + return "unordered elements are " + + ::Catch::Detail::stringify( m_desired ); + } + }; + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename RangeLike, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + RangeEqualsMatcher<RangeLike, Equality> + RangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename T, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + RangeEqualsMatcher<std::initializer_list<T>, Equality> + RangeEquals( std::initializer_list<T> range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in another range, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename RangeLike, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + UnorderedRangeEqualsMatcher<RangeLike, Equality> + UnorderedRangeEquals( RangeLike&& range, + Equality&& predicate = std::equal_to<>{} ) { + return { CATCH_FORWARD( range ), CATCH_FORWARD( predicate ) }; + } + + /** + * Creates a matcher that checks if all elements in a range are equal + * to all elements in an initializer list, in some permutation. + * + * Uses the provided predicate `predicate` to do the comparisons + * (defaulting to `std::equal_to`) + */ + template <typename T, + typename Equality = decltype( std::equal_to<>{} )> + constexpr + UnorderedRangeEqualsMatcher<std::initializer_list<T>, Equality> + UnorderedRangeEquals( std::initializer_list<T> range, + Equality&& predicate = std::equal_to<>{} ) { + return { range, CATCH_FORWARD( predicate ) }; + } + } // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_RANGE_EQUALS_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_STRING_HPP_INCLUDED +#define CATCH_MATCHERS_STRING_HPP_INCLUDED + + +#include <string> + +namespace Catch { +namespace Matchers { + + struct CasedString { + CasedString( std::string const& str, CaseSensitive caseSensitivity ); + std::string adjustString( std::string const& str ) const; + StringRef caseSensitivitySuffix() const; + + CaseSensitive m_caseSensitivity; + std::string m_str; + }; + + class StringMatcherBase : public MatcherBase<std::string> { + protected: + CasedString m_comparator; + StringRef m_operation; + + public: + StringMatcherBase( StringRef operation, + CasedString const& comparator ); + std::string describe() const override; + }; + + class StringEqualsMatcher final : public StringMatcherBase { + public: + StringEqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class StringContainsMatcher final : public StringMatcherBase { + public: + StringContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class StartsWithMatcher final : public StringMatcherBase { + public: + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + class EndsWithMatcher final : public StringMatcherBase { + public: + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + + class RegexMatcher final : public MatcherBase<std::string> { + std::string m_regex; + CaseSensitive m_caseSensitivity; + + public: + RegexMatcher( std::string regex, CaseSensitive caseSensitivity ); + bool match( std::string const& matchee ) const override; + std::string describe() const override; + }; + + //! Creates matcher that accepts strings that are exactly equal to `str` + StringEqualsMatcher Equals( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that contain `str` + StringContainsMatcher ContainsSubstring( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that _end_ with `str` + EndsWithMatcher EndsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings that _start_ with `str` + StartsWithMatcher StartsWith( std::string const& str, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + //! Creates matcher that accepts strings matching `regex` + RegexMatcher Matches( std::string const& regex, CaseSensitive caseSensitivity = CaseSensitive::Yes ); + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_STRING_HPP_INCLUDED + + +#ifndef CATCH_MATCHERS_VECTOR_HPP_INCLUDED +#define CATCH_MATCHERS_VECTOR_HPP_INCLUDED + + +#include <algorithm> + +namespace Catch { +namespace Matchers { + + template<typename T, typename Alloc> + class VectorContainsElementMatcher final : public MatcherBase<std::vector<T, Alloc>> { + T const& m_comparator; + + public: + VectorContainsElementMatcher(T const& comparator): + m_comparator(comparator) + {} + + bool match(std::vector<T, Alloc> const& v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } + + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class ContainsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + + public: + ContainsMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class EqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + + public: + EqualsMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if ( m_comparator.size() != v.size() ) { return false; } + for ( std::size_t i = 0; i < v.size(); ++i ) { + if ( !( m_comparator[i] == v[i] ) ) { return false; } + } + return true; + } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class ApproxMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_comparator; + mutable Catch::Approx approx = Catch::Approx::custom(); + + public: + ApproxMatcher(std::vector<T, AllocComp> const& comparator): + m_comparator( comparator ) + {} + + bool match(std::vector<T, AllocMatch> const& v) const override { + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != approx(v[i])) + return false; + return true; + } + std::string describe() const override { + return "is approx: " + ::Catch::Detail::stringify( m_comparator ); + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& epsilon( T const& newEpsilon ) { + approx.epsilon(static_cast<double>(newEpsilon)); + return *this; + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& margin( T const& newMargin ) { + approx.margin(static_cast<double>(newMargin)); + return *this; + } + template <typename = std::enable_if_t<std::is_constructible<double, T>::value>> + ApproxMatcher& scale( T const& newScale ) { + approx.scale(static_cast<double>(newScale)); + return *this; + } + }; + + template<typename T, typename AllocComp, typename AllocMatch> + class UnorderedEqualsMatcher final : public MatcherBase<std::vector<T, AllocMatch>> { + std::vector<T, AllocComp> const& m_target; + + public: + UnorderedEqualsMatcher(std::vector<T, AllocComp> const& target): + m_target(target) + {} + bool match(std::vector<T, AllocMatch> const& vec) const override { + if (m_target.size() != vec.size()) { + return false; + } + return std::is_permutation(m_target.begin(), m_target.end(), vec.begin()); + } + + std::string describe() const override { + return "UnorderedEquals: " + ::Catch::Detail::stringify(m_target); + } + }; + + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + + //! Creates a matcher that matches vectors that contain all elements in `comparator` + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) { + return ContainsMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that contain `comparator` as an element + template<typename T, typename Alloc = std::allocator<T>> + VectorContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) { + return VectorContainsElementMatcher<T, Alloc>(comparator); + } + + //! Creates a matcher that matches vectors that are exactly equal to `comparator` + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) { + return EqualsMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that `comparator` as an element + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) { + return ApproxMatcher<T, AllocComp, AllocMatch>(comparator); + } + + //! Creates a matcher that matches vectors that is equal to `target` modulo permutation + template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp> + UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) { + return UnorderedEqualsMatcher<T, AllocComp, AllocMatch>(target); + } + +} // namespace Matchers +} // namespace Catch + +#endif // CATCH_MATCHERS_VECTOR_HPP_INCLUDED + +#endif // CATCH_MATCHERS_ALL_HPP_INCLUDED + + +/** \file + * This is a convenience header for Catch2's Reporter support. It includes + * **all** of Catch2 headers related to reporters, including all reporters. + * + * Generally the Catch2 users should use specific includes they need, + * but this header can be used instead for ease-of-experimentation, or + * just plain convenience, at the cost of (significantly) increased + * compilation times. + * + * When a new header (reporter) is added to either the `reporter` folder, + * or to the corresponding internal subfolder, it should be added here. + */ + +#ifndef CATCH_REPORTERS_ALL_HPP_INCLUDED +#define CATCH_REPORTERS_ALL_HPP_INCLUDED + + + +#ifndef CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED +#define CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED + + + +#ifndef CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED +#define CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED + + + +#ifndef CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED +#define CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED + + +#include <map> +#include <string> + +namespace Catch { + class ColourImpl; + + /** + * This is the base class for all reporters. + * + * If are writing a reporter, you must derive from this type, or one + * of the helper reporter bases that are derived from this type. + * + * ReporterBase centralizes handling of various common tasks in reporters, + * like storing the right stream for the reporters to write to, and + * providing the default implementation of the different listing events. + */ + class ReporterBase : public IEventListener { + protected: + //! The stream wrapper as passed to us by outside code + Detail::unique_ptr<IStream> m_wrapped_stream; + //! Cached output stream from `m_wrapped_stream` to reduce + //! number of indirect calls needed to write output. + std::ostream& m_stream; + //! Colour implementation this reporter was configured for + Detail::unique_ptr<ColourImpl> m_colour; + //! The custom reporter options user passed down to the reporter + std::map<std::string, std::string> m_customOptions; + + public: + ReporterBase( ReporterConfig&& config ); + ~ReporterBase() override; // = default; + + /** + * Provides a simple default listing of reporters. + * + * Should look roughly like the reporter listing in v2 and earlier + * versions of Catch2. + */ + void listReporters( + std::vector<ReporterDescription> const& descriptions ) override; + /** + * Provides a simple default listing of listeners + * + * Looks similarly to listing of reporters, but with listener type + * instead of reporter name. + */ + void listListeners( + std::vector<ListenerDescription> const& descriptions ) override; + /** + * Provides a simple default listing of tests. + * + * Should look roughly like the test listing in v2 and earlier versions + * of Catch2. Especially supports low-verbosity listing that mimics the + * old `--list-test-names-only` output. + */ + void listTests( std::vector<TestCaseHandle> const& tests ) override; + /** + * Provides a simple default listing of tags. + * + * Should look roughly like the tag listing in v2 and earlier versions + * of Catch2. + */ + void listTags( std::vector<TagInfo> const& tags ) override; + }; +} // namespace Catch + +#endif // CATCH_REPORTER_COMMON_BASE_HPP_INCLUDED + +#include <vector> + +namespace Catch { + + class StreamingReporterBase : public ReporterBase { + public: + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + StreamingReporterBase(ReporterConfig&& _config): + ReporterBase(CATCH_MOVE(_config)) + {} + ~StreamingReporterBase() override; + + void benchmarkPreparing( StringRef ) override {} + void benchmarkStarting( BenchmarkInfo const& ) override {} + void benchmarkEnded( BenchmarkStats<> const& ) override {} + void benchmarkFailed( StringRef ) override {} + + void fatalErrorEncountered( StringRef /*error*/ ) override {} + void noMatchingTestCases( StringRef /*unmatchedSpec*/ ) override {} + void reportInvalidTestSpec( StringRef /*invalidArgument*/ ) override {} + + void testRunStarting( TestRunInfo const& _testRunInfo ) override; + + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = &_testInfo; + } + void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {} + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); + } + + void assertionStarting( AssertionInfo const& ) override {} + void assertionEnded( AssertionStats const& ) override {} + + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); + } + void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {} + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo = nullptr; + } + void testRunEnded( TestRunStats const& /* _testRunStats */ ) override; + + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + protected: + TestRunInfo currentTestRunInfo{ "test run has not started yet"_sr }; + TestCaseInfo const* currentTestCaseInfo = nullptr; + + //! Stack of all _active_ sections in the _current_ test case + std::vector<SectionInfo> m_sectionStack; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_STREAMING_BASE_HPP_INCLUDED + +#include <string> + +namespace Catch { + + class AutomakeReporter final : public StreamingReporterBase { + public: + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + AutomakeReporter(ReporterConfig&& _config): + StreamingReporterBase(CATCH_MOVE(_config)) + {} + ~AutomakeReporter() override; + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in the format of Automake .trs files"s; + } + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void skipTest(TestCaseInfo const& testInfo) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_AUTOMAKE_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_COMPACT_HPP_INCLUDED +#define CATCH_REPORTER_COMPACT_HPP_INCLUDED + + + + +namespace Catch { + + class CompactReporter final : public StreamingReporterBase { + public: + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription(); + + void noMatchingTestCases( StringRef unmatchedSpec ) override; + + void testRunStarting( TestRunInfo const& _testInfo ) override; + + void assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionEnded(SectionStats const& _sectionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_COMPACT_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_CONSOLE_HPP_INCLUDED +#define CATCH_REPORTER_CONSOLE_HPP_INCLUDED + + +namespace Catch { + // Fwd decls + class TablePrinter; + + class ConsoleReporter final : public StreamingReporterBase { + Detail::unique_ptr<TablePrinter> m_tablePrinter; + + public: + ConsoleReporter(ReporterConfig&& config); + ~ConsoleReporter() override; + static std::string getDescription(); + + void noMatchingTestCases( StringRef unmatchedSpec ) override; + void reportInvalidTestSpec( StringRef arg ) override; + + void assertionStarting(AssertionInfo const&) override; + + void assertionEnded(AssertionStats const& _assertionStats) override; + + void sectionStarting(SectionInfo const& _sectionInfo) override; + void sectionEnded(SectionStats const& _sectionStats) override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting(BenchmarkInfo const& info) override; + void benchmarkEnded(BenchmarkStats<> const& stats) override; + void benchmarkFailed( StringRef error ) override; + + void testCaseEnded(TestCaseStats const& _testCaseStats) override; + void testRunEnded(TestRunStats const& _testRunStats) override; + void testRunStarting(TestRunInfo const& _testRunInfo) override; + + private: + void lazyPrint(); + + void lazyPrintWithoutClosingBenchmarkTable(); + void lazyPrintRunInfo(); + void printTestCaseAndSectionHeader(); + + void printClosedHeader(std::string const& _name); + void printOpenHeader(std::string const& _name); + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString(std::string const& _string, std::size_t indent = 0); + + void printTotalsDivider(Totals const& totals); + + bool m_headerPrinted = false; + bool m_testRunInfoPrinted = false; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_CONSOLE_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED +#define CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED + + +#include <string> +#include <vector> + +namespace Catch { + + namespace Detail { + + //! Represents either an assertion or a benchmark result to be handled by cumulative reporter later + class AssertionOrBenchmarkResult { + // This should really be a variant, but this is much faster + // to write and the data layout here is already terrible + // enough that we do not have to care about the object size. + Optional<AssertionStats> m_assertion; + Optional<BenchmarkStats<>> m_benchmark; + public: + AssertionOrBenchmarkResult(AssertionStats const& assertion); + AssertionOrBenchmarkResult(BenchmarkStats<> const& benchmark); + + bool isAssertion() const; + bool isBenchmark() const; + + AssertionStats const& asAssertion() const; + BenchmarkStats<> const& asBenchmark() const; + }; + } + + /** + * Utility base for reporters that need to handle all results at once + * + * It stores tree of all test cases, sections and assertions, and after the + * test run is finished, calls into `testRunEndedCumulative` to pass the + * control to the deriving class. + * + * If you are deriving from this class and override any testing related + * member functions, you should first call into the base's implementation to + * avoid breaking the tree construction. + * + * Due to the way this base functions, it has to expand assertions up-front, + * even if they are later unused (e.g. because the deriving reporter does + * not report successful assertions, or because the deriving reporter does + * not use assertion expansion at all). Derived classes can use two + * customization points, `m_shouldStoreSuccesfulAssertions` and + * `m_shouldStoreFailedAssertions`, to disable the expansion and gain extra + * performance. **Accessing the assertion expansions if it wasn't stored is + * UB.** + */ + class CumulativeReporterBase : public ReporterBase { + public: + template<typename T, typename ChildNodeT> + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + + using ChildNodes = std::vector<Detail::unique_ptr<ChildNodeT>>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + + bool hasAnyAssertions() const; + + SectionStats stats; + std::vector<Detail::unique_ptr<SectionNode>> childSections; + std::vector<Detail::AssertionOrBenchmarkResult> assertionsAndBenchmarks; + std::string stdOut; + std::string stdErr; + }; + + + using TestCaseNode = Node<TestCaseStats, SectionNode>; + using TestRunNode = Node<TestRunStats, TestCaseNode>; + + // GCC5 compat: we cannot use inherited constructor, because it + // doesn't implement backport of P0136 + CumulativeReporterBase(ReporterConfig&& _config): + ReporterBase(CATCH_MOVE(_config)) + {} + ~CumulativeReporterBase() override; + + void benchmarkPreparing( StringRef ) override {} + void benchmarkStarting( BenchmarkInfo const& ) override {} + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed( StringRef ) override {} + + void noMatchingTestCases( StringRef ) override {} + void reportInvalidTestSpec( StringRef ) override {} + void fatalErrorEncountered( StringRef /*error*/ ) override {} + + void testRunStarting( TestRunInfo const& ) override {} + + void testCaseStarting( TestCaseInfo const& ) override {} + void testCasePartialStarting( TestCaseInfo const&, uint64_t ) override {} + void sectionStarting( SectionInfo const& sectionInfo ) override; + + void assertionStarting( AssertionInfo const& ) override {} + + void assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded( TestCaseStats const&, uint64_t ) override {} + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + //! Customization point: called after last test finishes (testRunEnded has been handled) + virtual void testRunEndedCumulative() = 0; + + void skipTest(TestCaseInfo const&) override {} + + protected: + //! Should the cumulative base store the assertion expansion for successful assertions? + bool m_shouldStoreSuccesfulAssertions = true; + //! Should the cumulative base store the assertion expansion for failed assertions? + bool m_shouldStoreFailedAssertions = true; + + // We need lazy construction here. We should probably refactor it + // later, after the events are redone. + //! The root node of the test run tree. + Detail::unique_ptr<TestRunNode> m_testRun; + + private: + // Note: We rely on pointer identity being stable, which is why + // we store pointers to the nodes rather than the values. + std::vector<Detail::unique_ptr<TestCaseNode>> m_testCases; + // Root section of the _current_ test case + Detail::unique_ptr<SectionNode> m_rootSection; + // Deepest section of the _current_ test case + SectionNode* m_deepestSection = nullptr; + // Stack of _active_ sections in the _current_ test case + std::vector<SectionNode*> m_sectionStack; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_CUMULATIVE_BASE_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED +#define CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED + + +namespace Catch { + + /** + * Base class to simplify implementing listeners. + * + * Provides empty default implementation for all IEventListener member + * functions, so that a listener implementation can pick which + * member functions it actually cares about. + */ + class EventListenerBase : public IEventListener { + public: + using IEventListener::IEventListener; + + void reportInvalidTestSpec( StringRef unmatchedSpec ) override; + void fatalErrorEncountered( StringRef error ) override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed( StringRef error ) override; + + void assertionStarting( AssertionInfo const& assertionInfo ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; + + void listReporters( + std::vector<ReporterDescription> const& descriptions ) override; + void listListeners( + std::vector<ListenerDescription> const& descriptions ) override; + void listTests( std::vector<TestCaseHandle> const& tests ) override; + void listTags( std::vector<TagInfo> const& tagInfos ) override; + + void noMatchingTestCases( StringRef unmatchedSpec ) override; + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void testCasePartialStarting( TestCaseInfo const& testInfo, + uint64_t partNumber ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded( TestCaseStats const& testCaseStats, + uint64_t partNumber ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + void skipTest( TestCaseInfo const& testInfo ) override; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_EVENT_LISTENER_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_HELPERS_HPP_INCLUDED +#define CATCH_REPORTER_HELPERS_HPP_INCLUDED + +#include <iosfwd> +#include <string> +#include <vector> + + +namespace Catch { + + class IConfig; + class TestCaseHandle; + class ColourImpl; + + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); + + //! Should the reporter show duration of test given current configuration? + bool shouldShowDuration( IConfig const& config, double duration ); + + std::string serializeFilters( std::vector<std::string> const& filters ); + + struct lineOfChars { + char c; + constexpr lineOfChars( char c_ ): c( c_ ) {} + + friend std::ostream& operator<<( std::ostream& out, lineOfChars value ); + }; + + /** + * Lists reporter descriptions to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void + defaultListReporters( std::ostream& out, + std::vector<ReporterDescription> const& descriptions, + Verbosity verbosity ); + + /** + * Lists listeners descriptions to the provided stream in user-friendly + * format + */ + void defaultListListeners( std::ostream& out, + std::vector<ListenerDescription> const& descriptions ); + + /** + * Lists tag information to the provided stream in user-friendly format + * + * Used as the default listing implementation by the first party reporter + * bases. The output should be backwards compatible with the output of + * Catch2 v2 binaries. + */ + void defaultListTags( std::ostream& out, std::vector<TagInfo> const& tags, bool isFiltered ); + + /** + * Lists test case information to the provided stream in user-friendly + * format + * + * Used as the default listing implementation by the first party reporter + * bases. The output is backwards compatible with the output of Catch2 + * v2 binaries, and also supports the format specific to the old + * `--list-test-names-only` option, for people who used it in integrations. + */ + void defaultListTests( std::ostream& out, + ColourImpl* streamColour, + std::vector<TestCaseHandle> const& tests, + bool isFiltered, + Verbosity verbosity ); + + /** + * Prints test run totals to the provided stream in user-friendly format + * + * Used by the console and compact reporters. + */ + void printTestRunTotals( std::ostream& stream, + ColourImpl& streamColour, + Totals const& totals ); + +} // end namespace Catch + +#endif // CATCH_REPORTER_HELPERS_HPP_INCLUDED + + + +#ifndef CATCH_REPORTER_JSON_HPP_INCLUDED +#define CATCH_REPORTER_JSON_HPP_INCLUDED + + +#include <stack> + +namespace Catch { + class JsonReporter : public StreamingReporterBase { + public: + JsonReporter( ReporterConfig&& config ); + + ~JsonReporter() override; + + static std::string getDescription(); + + public: // StreamingReporterBase + void testRunStarting( TestRunInfo const& runInfo ) override; + void testRunEnded( TestRunStats const& runStats ) override; + + void testCaseStarting( TestCaseInfo const& tcInfo ) override; + void testCaseEnded( TestCaseStats const& tcStats ) override; + + void testCasePartialStarting( TestCaseInfo const& tcInfo, + uint64_t index ) override; + void testCasePartialEnded( TestCaseStats const& tcStats, + uint64_t index ) override; + + void sectionStarting( SectionInfo const& sectionInfo ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + + void assertionStarting( AssertionInfo const& assertionInfo ) override; + void assertionEnded( AssertionStats const& assertionStats ) override; + + //void testRunEndedCumulative() override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& ) override; + void benchmarkEnded( BenchmarkStats<> const& ) override; + void benchmarkFailed( StringRef error ) override; + + void listReporters( + std::vector<ReporterDescription> const& descriptions ) override; + void listListeners( + std::vector<ListenerDescription> const& descriptions ) override; + void listTests( std::vector<TestCaseHandle> const& tests ) override; + void listTags( std::vector<TagInfo> const& tags ) override; + + private: + Timer m_testCaseTimer; + enum class Writer { + Object, + Array + }; + + JsonArrayWriter& startArray(); + JsonArrayWriter& startArray( StringRef key ); + + JsonObjectWriter& startObject(); + JsonObjectWriter& startObject( StringRef key ); + + void endObject(); + void endArray(); + + bool isInside( Writer writer ); + + void startListing(); + void endListing(); + + // Invariant: + // When m_writers is not empty and its top element is + // - Writer::Object, then m_objectWriters is not be empty + // - Writer::Array, then m_arrayWriters shall not be empty + std::stack<JsonObjectWriter> m_objectWriters{}; + std::stack<JsonArrayWriter> m_arrayWriters{}; + std::stack<Writer> m_writers{}; + + bool m_startedListing = false; + + // std::size_t m_sectionDepth = 0; + // std::size_t m_sectionStarted = 0; + }; +} // namespace Catch + +#endif // CATCH_REPORTER_JSON_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_JUNIT_HPP_INCLUDED +#define CATCH_REPORTER_JUNIT_HPP_INCLUDED + + + +namespace Catch { + + class JunitReporter final : public CumulativeReporterBase { + public: + JunitReporter(ReporterConfig&& _config); + + static std::string getDescription(); + + void testRunStarting(TestRunInfo const& runInfo) override; + + void testCaseStarting(TestCaseInfo const& testCaseInfo) override; + void assertionEnded(AssertionStats const& assertionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testRunEndedCumulative() override; + + private: + void writeRun(TestRunNode const& testRunNode, double suiteTime); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode, + bool testOkToFail ); + + void writeAssertions(SectionNode const& sectionNode); + void writeAssertion(AssertionStats const& stats); + + XmlWriter xml; + Timer suiteTimer; + std::string stdOutForSuite; + std::string stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_JUNIT_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_MULTI_HPP_INCLUDED +#define CATCH_REPORTER_MULTI_HPP_INCLUDED + + +namespace Catch { + + class MultiReporter final : public IEventListener { + /* + * Stores all added reporters and listeners + * + * All Listeners are stored before all reporters, and individual + * listeners/reporters are stored in order of insertion. + */ + std::vector<IEventListenerPtr> m_reporterLikes; + bool m_haveNoncapturingReporters = false; + + // Keep track of how many listeners we have already inserted, + // so that we can insert them into the main vector at the right place + size_t m_insertedListeners = 0; + + void updatePreferences(IEventListener const& reporterish); + + public: + using IEventListener::IEventListener; + + void addListener( IEventListenerPtr&& listener ); + void addReporter( IEventListenerPtr&& reporter ); + + public: // IEventListener + + void noMatchingTestCases( StringRef unmatchedSpec ) override; + void fatalErrorEncountered( StringRef error ) override; + void reportInvalidTestSpec( StringRef arg ) override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats<> const& benchmarkStats ) override; + void benchmarkFailed( StringRef error ) override; + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void testCasePartialStarting(TestCaseInfo const& testInfo, uint64_t partNumber) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; + + void assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCasePartialEnded(TestCaseStats const& testStats, uint64_t partNumber) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; + + void skipTest( TestCaseInfo const& testInfo ) override; + + void listReporters(std::vector<ReporterDescription> const& descriptions) override; + void listListeners(std::vector<ListenerDescription> const& descriptions) override; + void listTests(std::vector<TestCaseHandle> const& tests) override; + void listTags(std::vector<TagInfo> const& tags) override; + + + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_MULTI_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_REGISTRARS_HPP_INCLUDED +#define CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + + +#include <type_traits> + +namespace Catch { + + namespace Detail { + + template <typename T, typename = void> + struct has_description : std::false_type {}; + + template <typename T> + struct has_description< + T, + void_t<decltype( T::getDescription() )>> + : std::true_type {}; + + //! Indirection for reporter registration, so that the error handling is + //! independent on the reporter's concrete type + void registerReporterImpl( std::string const& name, + IReporterFactoryPtr reporterPtr ); + //! Actually registers the factory, independent on listener's concrete type + void registerListenerImpl( Detail::unique_ptr<EventListenerFactory> listenerFactory ); + } // namespace Detail + + class IEventListener; + using IEventListenerPtr = Detail::unique_ptr<IEventListener>; + + template <typename T> + class ReporterFactory : public IReporterFactory { + + IEventListenerPtr create( ReporterConfig&& config ) const override { + return Detail::make_unique<T>( CATCH_MOVE(config) ); + } + + std::string getDescription() const override { + return T::getDescription(); + } + }; + + + template<typename T> + class ReporterRegistrar { + public: + explicit ReporterRegistrar( std::string const& name ) { + registerReporterImpl( name, + Detail::make_unique<ReporterFactory<T>>() ); + } + }; + + template<typename T> + class ListenerRegistrar { + + class TypedListenerFactory : public EventListenerFactory { + StringRef m_listenerName; + + std::string getDescriptionImpl( std::true_type ) const { + return T::getDescription(); + } + + std::string getDescriptionImpl( std::false_type ) const { + return "(No description provided)"; + } + + public: + TypedListenerFactory( StringRef listenerName ): + m_listenerName( listenerName ) {} + + IEventListenerPtr create( IConfig const* config ) const override { + return Detail::make_unique<T>( config ); + } + + StringRef getName() const override { + return m_listenerName; + } + + std::string getDescription() const override { + return getDescriptionImpl( Detail::has_description<T>{} ); + } + }; + + public: + ListenerRegistrar(StringRef listenerName) { + registerListenerImpl( Detail::make_unique<TypedListenerFactory>(listenerName) ); + } + }; +} + +#if !defined(CATCH_CONFIG_DISABLE) + +# define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + Catch::ReporterRegistrar<reporterType> INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_RegistrarFor )( name ); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +# define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace { \ + Catch::ListenerRegistrar<listenerType> INTERNAL_CATCH_UNIQUE_NAME( \ + catch_internal_RegistrarFor )( #listenerType##_catch_sr ); \ + } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +#else // CATCH_CONFIG_DISABLE + +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) + +#endif // CATCH_CONFIG_DISABLE + +#endif // CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_SONARQUBE_HPP_INCLUDED +#define CATCH_REPORTER_SONARQUBE_HPP_INCLUDED + + + +namespace Catch { + + class SonarQubeReporter final : public CumulativeReporterBase { + public: + SonarQubeReporter(ReporterConfig&& config) + : CumulativeReporterBase(CATCH_MOVE(config)) + , xml(m_stream) { + m_preferences.shouldRedirectStdOut = true; + m_preferences.shouldReportAllAssertions = false; + m_shouldStoreSuccesfulAssertions = false; + } + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in the Generic Test Data SonarQube XML format"s; + } + + void testRunStarting( TestRunInfo const& testRunInfo ) override; + + void testRunEndedCumulative() override { + writeRun( *m_testRun ); + xml.endElement(); + } + + void writeRun( TestRunNode const& runNode ); + + void writeTestFile(StringRef filename, std::vector<TestCaseNode const*> const& testCaseNodes); + + void writeTestCase(TestCaseNode const& testCaseNode); + + void writeSection(std::string const& rootName, SectionNode const& sectionNode, bool okToFail); + + void writeAssertions(SectionNode const& sectionNode, bool okToFail); + + void writeAssertion(AssertionStats const& stats, bool okToFail); + + private: + XmlWriter xml; + }; + + +} // end namespace Catch + +#endif // CATCH_REPORTER_SONARQUBE_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_TAP_HPP_INCLUDED +#define CATCH_REPORTER_TAP_HPP_INCLUDED + + +namespace Catch { + + class TAPReporter final : public StreamingReporterBase { + public: + TAPReporter( ReporterConfig&& config ): + StreamingReporterBase( CATCH_MOVE(config) ) { + m_preferences.shouldReportAllAssertions = true; + } + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results in TAP format, suitable for test harnesses"s; + } + + void testRunStarting( TestRunInfo const& testInfo ) override; + + void noMatchingTestCases( StringRef unmatchedSpec ) override; + + void assertionEnded(AssertionStats const& _assertionStats) override; + + void testRunEnded(TestRunStats const& _testRunStats) override; + + private: + std::size_t counter = 0; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_TAP_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_TEAMCITY_HPP_INCLUDED +#define CATCH_REPORTER_TEAMCITY_HPP_INCLUDED + + +#include <cstring> + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class TeamCityReporter final : public StreamingReporterBase { + public: + TeamCityReporter( ReporterConfig&& _config ) + : StreamingReporterBase( CATCH_MOVE(_config) ) + { + m_preferences.shouldRedirectStdOut = true; + } + + ~TeamCityReporter() override; + + static std::string getDescription() { + using namespace std::string_literals; + return "Reports test results as TeamCity service messages"s; + } + + void testRunStarting( TestRunInfo const& runInfo ) override; + void testRunEnded( TestRunStats const& runStats ) override; + + + void assertionEnded(AssertionStats const& assertionStats) override; + + void sectionStarting(SectionInfo const& sectionInfo) override { + m_headerPrintedForThisSection = false; + StreamingReporterBase::sectionStarting( sectionInfo ); + } + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + private: + void printSectionHeader(std::ostream& os); + + bool m_headerPrintedForThisSection = false; + Timer m_testTimer; + }; + +} // end namespace Catch + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif + +#endif // CATCH_REPORTER_TEAMCITY_HPP_INCLUDED + + +#ifndef CATCH_REPORTER_XML_HPP_INCLUDED +#define CATCH_REPORTER_XML_HPP_INCLUDED + + + + +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter(ReporterConfig&& _config); + + ~XmlReporter() override; + + static std::string getDescription(); + + virtual std::string getStylesheetRef() const; + + void writeSourceInfo(SourceLineInfo const& sourceInfo); + + public: // StreamingReporterBase + + void testRunStarting(TestRunInfo const& testInfo) override; + + void testCaseStarting(TestCaseInfo const& testInfo) override; + + void sectionStarting(SectionInfo const& sectionInfo) override; + + void assertionStarting(AssertionInfo const&) override; + + void assertionEnded(AssertionStats const& assertionStats) override; + + void sectionEnded(SectionStats const& sectionStats) override; + + void testCaseEnded(TestCaseStats const& testCaseStats) override; + + void testRunEnded(TestRunStats const& testRunStats) override; + + void benchmarkPreparing( StringRef name ) override; + void benchmarkStarting(BenchmarkInfo const&) override; + void benchmarkEnded(BenchmarkStats<> const&) override; + void benchmarkFailed( StringRef error ) override; + + void listReporters(std::vector<ReporterDescription> const& descriptions) override; + void listListeners(std::vector<ListenerDescription> const& descriptions) override; + void listTests(std::vector<TestCaseHandle> const& tests) override; + void listTags(std::vector<TagInfo> const& tags) override; + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; + }; + +} // end namespace Catch + +#endif // CATCH_REPORTER_XML_HPP_INCLUDED + +#endif // CATCH_REPORTERS_ALL_HPP_INCLUDED + +#endif // CATCH_ALL_HPP_INCLUDED +#endif // CATCH_AMALGAMATED_HPP_INCLUDED diff --git a/tests/compile_flags.txt b/tests/compile_flags.txt new file mode 100644 index 00000000..c0bd3f85 --- /dev/null +++ b/tests/compile_flags.txt @@ -0,0 +1,58 @@ +-I../include +-Wall +-Wextra +-Wpedantic +-std=c++20 +-DVERSION="1.0.0" +-DGUI_APP=1 +-DUSE_DCONF=1 +-DDEBUG=1 +-DENABLE_NLS=1 +-DPREFIX="/usr" +-DLOCALEDIR="/usr/share/locale" +-I./useless_stuff/ +-I/usr/include/gtkmm-3.0 +-I/usr/lib/gtkmm-3.0/include +-I/usr/include/gtk-3.0 +-I/usr/include/pango-1.0 +-I/usr/include/glib-2.0 +-I/usr/lib/glib-2.0/include +-I/usr/include/harfbuzz +-I/usr/include/freetype2 +-I/usr/include/sigc++-2.0 +-I/usr/lib/sigc++-2.0/include +-I/usr/include/atkmm-1.6 +-I/usr/lib/atkmm-1.6/include +-I/usr/include/glibmm-2.4 +-I/usr/lib/glibmm-2.4/include +-I/usr/include/gtk-3.0/unix-print +-I/usr/include/atk-1.0 +-I/usr/include/cairo +-I/usr/include/gdkmm-3.0 +-I/usr/lib/gdkmm-3.0/include +-I/usr/include/giomm-2.4 +-I/usr/lib/giomm-2.4/include +-I/usr/include/cairomm-1.0 +-I/usr/lib/cairomm-1.0/include +-I/usr/include/pangomm-1.4 +-I/usr/lib/pangomm-1.4/include +-I/usr/include/gdk-pixbuf-2.0 +-I/usr/include/libmount +-I/usr/include/blkid +-I/usr/include/sysprof-6 +-pthread +-I/usr/include/libpng16 +-I/usr/include/pixman-1 +-I/usr/include/cloudproviders +-I/usr/include/at-spi2-atk/2.0 +-I/usr/include/at-spi-2.0 +-I/usr/include/dbus-1.0 +-I/usr/lib/dbus-1.0/include +-I/usr/include/fribidi +-I/usr/include/gio-unix-2.0 +-I/usr/include/dconf +-I/usr/include/glib-2.0 +-I/usr/lib/glib-2.0/include +-I/usr/include/libmount +-I/usr/include/blkid +-I/usr/include/sysprof-6 diff --git a/tests/test_util.cpp b/tests/test_util.cpp new file mode 100644 index 00000000..a5affefd --- /dev/null +++ b/tests/test_util.cpp @@ -0,0 +1,51 @@ +/* + * Copyright 2025 Toni500git + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following + * disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the + * following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include <string> +#include "util.hpp" + +#include "catch2/catch_amalgamated.hpp" +std::string tmp; + +TEST_CASE( "util.cpp test suitcase", "[Util]" ) { + SECTION( "String asserts" ) { + std::string strip_str{" strip_\tthis_\nstring"}; + strip(strip_str); + std::string replace_str_str{"replace foo and foo with bar"}; + replace_str(replace_str_str, "foo", "bar"); + std::string env = std::getenv("HOME"); + std::string path = "~/.config/rule34"; + + REQUIRE(hasEnding("I want the end", "end")); + REQUIRE(hasStart("And now the begin, then I want the end", "And")); + REQUIRE(expandVar(path) == env + "/.config/rule34"); + REQUIRE(read_shell_exec("echo hello") == "hello"); + REQUIRE(split("this;should;be;a;vector", ';') == std::vector<std::string>{"this", "should", "be", "a", "vector"}); + REQUIRE(strip_str == "strip_this_string"); + REQUIRE(replace_str_str == "replace bar and bar with bar"); + REQUIRE(str_tolower("ThIS SHouLD Be LOWER") == "this should be lower"); + REQUIRE(str_toupper("ThIS SHouLD Be UPPER") == "THIS SHOULD BE UPPER"); + } +}