diff --git a/.cargo/config b/.cargo/config deleted file mode 100644 index 35049cbcb..000000000 --- a/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[alias] -xtask = "run --package xtask --" diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..17177ad0a --- /dev/null +++ b/.clang-format @@ -0,0 +1,5 @@ +--- +Language: Cpp +BasedOnStyle: Google +ColumnLimit: 100 +... diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 385cfafb7..84685922f 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -10,11 +10,11 @@ RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ fi \ && rm -f /tmp/reinstall-cmake.sh -RUN sudo apt update && sudo apt install uuid-dev +RUN sudo apt-get update && sudo apt-get install uuid-dev faketime # [Optional] Uncomment this section to install additional vcpkg ports. # RUN su vscode -c "${VCPKG_ROOT}/vcpkg install " # [Optional] Uncomment this section to install additional packages. # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ -# && apt-get -y install --no-install-recommends \ No newline at end of file +# && apt-get -y install --no-install-recommends diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..eb5a316cb --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +target diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..8c8b72b3e --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# initial bulk run of formatting with pre-commit +93356b39c3086fdf8dd41d7357bb1c115ff69cb1 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index a945a565d..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,3 +0,0 @@ -taskchampion/* @dbr @djmitche -Cargo.toml @dbr @djmitche -Cargo.lock @dbr @djmitche diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4490fcc9a..6e2bb8297 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,11 @@ updates: directory: "/" schedule: interval: "weekly" + # Enable version updates for git submodules + - package-ecosystem: "gitsubmodule" + directory: "/" + schedule: + interval: "daily" # Enable updates for Rust packages - package-ecosystem: "cargo" directory: "/" # Location of package manifests diff --git a/.github/issue_template.md b/.github/issue_template.md index b65581ad7..4d80be6cf 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -9,4 +9,4 @@ * Clearly describe the feature. * Clearly state the use case. We are only interested in use cases, do not waste time with implementation details or suggested syntax. -* Please see our notes on [How to request a feature](https://taskwarrior.org/docs/features.html) +* Please see our notes on [How to request a feature](https://taskwarrior.org/docs/features/) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 724bf7829..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,11 +0,0 @@ -#### Description - -Replace this text with a description of the PR. - -#### Additional information... - -- [ ] I changed C++ code or build infrastructure. - Please run the test suite and include the output of `cd test && ./problems`. - -- [ ] I changed Rust code or build infrastructure. - Please run `cargo test` and address any failures before submitting. diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 96553404a..c99fae36a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -15,22 +15,25 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + id: toolchain + with: + # If this version is old enough to cause errors, or older than the + # TaskChampion MSRV, bump it to the MSRV of the currently-required + # TaskChampion package; if necessary, bump that version as well. + toolchain: "1.81.0" # MSRV + - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v4 with: path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: "1.70.0" # MSRV - override: true + key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - uses: actions-rs/cargo@v1.0.3 with: @@ -44,72 +47,34 @@ jobs: args: --all-features name: "Clippy Results" - mdbook: - runs-on: ubuntu-latest - name: "Documentation" - - steps: - - uses: actions/checkout@v4 - - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v2 - with: - # if this changes, change it in .github/workflows/publish-docs.yml as well - mdbook-version: '0.4.10' - - - run: mdbook test taskchampion/docs - - run: mdbook build taskchampion/docs - fmt: runs-on: ubuntu-latest name: "Formatting" steps: - uses: actions/checkout@v4 - - uses: actions-rs/toolchain@v1 + - uses: dtolnay/rust-toolchain@master + id: toolchain with: - profile: minimal - components: rustfmt - toolchain: stable - override: true + toolchain: "stable" + components: "rustfmt" - uses: actions-rs/cargo@v1.0.3 with: command: fmt args: --all -- --check - codegen: + cargo-metadata: runs-on: ubuntu-latest - name: "codegen" + name: "Cargo Metadata" steps: - uses: actions/checkout@v4 - - name: Cache cargo registry - uses: actions/cache@v4 + - uses: dtolnay/rust-toolchain@master + id: toolchain with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + toolchain: "stable" + components: "rustfmt" - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: "1.70.0" # MSRV - override: true - - - uses: actions-rs/cargo@v1.0.3 - with: - command: run - args: --package xtask -- codegen - - - name: check for changes - run: | - if ! git diff; then - echo "Generated code not up-to-date; - run `cargo run --package xtask -- codegen` and commit the result"; - exit 1; - fi + - name: "Check metadata" + run: ".github/workflows/metadata-check.sh" diff --git a/.github/workflows/docker-image.yaml b/.github/workflows/docker-image.yaml index 375f8ea62..63a0f976c 100644 --- a/.github/workflows/docker-image.yaml +++ b/.github/workflows/docker-image.yaml @@ -23,31 +23,35 @@ jobs: id-token: write steps: + - name: Create lowercase repository name + run: | + GHCR_REPOSITORY="${{ github.repository_owner }}" + echo "REPOSITORY=${GHCR_REPOSITORY,,}" >> ${GITHUB_ENV} - name: Checkout repository uses: actions/checkout@v4 with: submodules: "recursive" - name: Install cosign - uses: sigstore/cosign-installer@v3.5.0 + uses: sigstore/cosign-installer@v3.9.0 - name: Log into registry ${{ env.REGISTRY }} - uses: docker/login-action@v3.1.0 + uses: docker/login-action@v3.4.0 with: registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} + username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push Taskwarrior Docker image id: build-and-push - uses: docker/build-push-action@v5.3.0 + uses: docker/build-push-action@v6.18.0 with: context: . file: "./docker/task.dockerfile" push: true - tags: ${{ env.REGISTRY }}/${{ github.actor }}/task:${{ github.ref_name }} + tags: ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task:${{ github.ref_name }} - name: Sign the published Docker image env: COSIGN_EXPERIMENTAL: "true" - run: cosign sign ${{ env.REGISTRY }}/${{ github.actor }}/task@${{ steps.build-and-push.outputs.digest }} + run: cosign sign ${{ env.REGISTRY }}/${{ env.REPOSITORY }}/task@${{ steps.build-and-push.outputs.digest }} diff --git a/.github/workflows/metadata-check.sh b/.github/workflows/metadata-check.sh new file mode 100755 index 000000000..e22d43453 --- /dev/null +++ b/.github/workflows/metadata-check.sh @@ -0,0 +1,32 @@ +#! /bin/bash + +# Check the 'cargo metadata' for various requirements + +set -e + +META=$(mktemp) +trap 'rm -rf -- "${META}"' EXIT + +cargo metadata --locked --format-version 1 > "${META}" + +get_msrv() { + local package="${1}" + jq -r '.packages[] | select(.name == "'"${package}"'") | .rust_version' "${META}" +} + +check_msrv() { + local taskchampion_msrv=$(get_msrv taskchampion) + local taskchampion_lib_msrv=$(get_msrv taskchampion-lib) + + echo "Found taskchampion MSRV ${taskchampion_msrv}" + echo "Found taskchampion-lib MSRV ${taskchampion_lib_msrv}" + + if [ "${taskchampion_msrv}" != "${taskchampion_lib_msrv}" ]; then + echo "Those MSRVs should be the same (or taskchampion-lib should be greater, in which case adjust this script)" + exit 1 + else + echo "✓ MSRVs are at the same version." + fi +} + +check_msrv diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml deleted file mode 100644 index dfa4c54e7..000000000 --- a/.github/workflows/publish-docs.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: docs - -on: - push: - branches: - - develop - -permissions: - contents: write - -jobs: - mdbook-deploy: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Setup mdBook - uses: peaceiris/actions-mdbook@v2 - with: - # if this changes, change it in .github/workflows/checks.yml as well - mdbook-version: '0.4.10' - - - run: mdbook build taskchampion/docs - - - name: Deploy - uses: peaceiris/actions-gh-pages@v4 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./taskchampion/docs/book - destination_dir: taskchampion diff --git a/.github/workflows/release-check.yaml b/.github/workflows/release-check.yaml new file mode 100644 index 000000000..57cfa0dc7 --- /dev/null +++ b/.github/workflows/release-check.yaml @@ -0,0 +1,28 @@ +name: release-tests +on: [push, pull_request] +jobs: + check-tarball: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@stable + id: toolchain + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Install uuid-dev + run: sudo apt install uuid-dev + + - name: make a release tarball and build from it + run: | + cmake -S. -Bbuild && + make -Cbuild package_source && + tar -xf build/task-*.tar.gz && + cd task-*.*.* && + cmake -S. -Bbuild && + cmake --build build --target task_executable diff --git a/.github/workflows/rust-tests.yml b/.github/workflows/rust-tests.yml deleted file mode 100644 index cf11d390c..000000000 --- a/.github/workflows/rust-tests.yml +++ /dev/null @@ -1,83 +0,0 @@ - -name: tests - rust - -on: - push: - branches: - - develop - pull_request: - types: [opened, reopened, synchronize] - -jobs: - ## Run the `taskchampion` crate's tests with various combinations of features. - features: - strategy: - matrix: - features: - - "" - - "server-sync" - - name: "taskchampion ${{ matrix.features == '' && 'with no features' || format('with features {0}', matrix.features) }}" - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ubuntu-latest-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ubuntu-latest-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: test - run: cargo test -p taskchampion --no-default-features --features "${{ matrix.features }}" - - ## Run all TaskChampion crate tests, using both the minimum supported rust version - ## and the latest stable Rust. - test: - strategy: - matrix: - rust: - - "1.70.0" # MSRV - - "stable" - os: - - ubuntu-latest - - macOS-latest - - windows-latest - - name: "rust ${{ matrix.rust }} on ${{ matrix.os }}" - runs-on: ${{ matrix.os }} - - steps: - - uses: actions/checkout@v4 - - - name: Cache cargo registry - uses: actions/cache@v4 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-${{ matrix.rust }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v4 - with: - path: target - key: ${{ runner.os }}-${{ matrix.rust }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: "${{ matrix.rust }}" - override: true - - - name: test - run: cargo test diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index cd3f64d83..bdc3347ee 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,77 +1,138 @@ ## Run the Taskwarrior tests, using stable rust to build TaskChampion. - name: tests + on: [push, pull_request] + jobs: + coverage: + runs-on: ubuntu-latest + steps: + - name: Install apt packages + run: sudo apt-get install -y build-essential cmake git uuid-dev faketime locales python3 curl gcovr ninja-build + + - name: Check out this repository + uses: actions/checkout@v4.1.6 + + - name: Configure project + run: cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS=--coverage + + - name: Build project + run: cmake --build build --target test_runner --target task_executable + + - name: Test project + run: ctest --test-dir build -j 8 --output-on-failure + + - name: Generate a code coverage report + uses: threeal/gcovr-action@v1.1.0 + with: + coveralls-out: coverage.coveralls.json + excludes: | + build + + - name: Sent to Coveralls + uses: coverallsapp/github-action@v2 + with: + file: coverage.coveralls.json + # MacOS tests do not run in Docker, and use the actions-rs Rust installaction tests-macos-12: + needs: coverage name: tests (Mac OS 12.latest) if: false # see #3242 runs-on: macos-latest steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + id: toolchain + - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry - key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v4 with: path: target - key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: "stable" - override: true + key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - name: Test MacOS run: bash test/scripts/test_macos.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} tests-macos-13: + needs: coverage name: tests (Mac OS 13.latest) if: false # see #3242 runs-on: macos-13 steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + id: toolchain + - name: Cache cargo registry uses: actions/cache@v4 with: path: ~/.cargo/registry - key: ${{ runner.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - name: Cache cargo build uses: actions/cache@v4 with: path: target - key: ${{ runner.os }}-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} - - - uses: actions-rs/toolchain@v1 - with: - toolchain: "stable" - override: true + key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} - name: Test MacOS run: bash test/scripts/test_macos.sh env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + cargo-test: + runs-on: ubuntu-latest + name: "Cargo Test" + + steps: + - uses: actions/checkout@v4 + + - uses: dtolnay/rust-toolchain@master + id: toolchain + with: + # If this version is old enough to cause errors, or older than the + # TaskChampion MSRV, bump it to the MSRV of the currently-required + # TaskChampion package; if necessary, bump that version as well. + toolchain: "1.81.0" # MSRV + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: ~/.cargo/registry + key: ${{ runner.os }}-cargo-registry-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + + - name: Cache cargo build + uses: actions/cache@v4 + with: + path: target + key: ${{ runner.os }}-cargo-build-target-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('**/Cargo.lock') }} + + - uses: actions-rs/cargo@v1.0.3 + with: + command: test + tests: + needs: coverage strategy: fail-fast: false matrix: include: - - name: "Fedora 38" + - name: "Fedora 40" runner: ubuntu-latest - dockerfile: fedora38 - - name: "Fedora 39" + dockerfile: fedora40 + - name: "Fedora 41" runner: ubuntu-latest - dockerfile: fedora39 + dockerfile: fedora41 - name: "Debian Testing" runner: ubuntu-latest dockerfile: debiantesting @@ -99,10 +160,10 @@ jobs: GITHUB_USER: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CONTAINER: ${{ matrix.dockerfile }} - run: docker-compose build test-$CONTAINER + run: docker compose build test-${{ env.CONTAINER }} - name: Test ${{ matrix.name }} - run: docker-compose run test-$CONTAINER + run: docker compose run test-${{ env.CONTAINER }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CONTAINER: ${{ matrix.dockerfile }} diff --git a/.gitignore b/.gitignore index af7096a46..bc63804f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,12 @@ cmake.h commit.h +.cache *~ .*.swp Session.vim package-config/osx/binary/task -/build*/ -install_manifest.txt -_CPack_Packages +/*build*/ patches -*.exe tutorials .prove /target/ diff --git a/.gitmodules b/.gitmodules index cddc791b0..bcd2aa1c5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "src/libshared"] path = src/libshared url = https://github.com/GothenburgBitFactory/libshared.git -[submodule "src/tc/corrosion"] - path = src/tc/corrosion +[submodule "src/taskchampion-cpp/corrosion"] + path = src/taskchampion-cpp/corrosion url = https://github.com/corrosion-rs/corrosion.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..f17f48519 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,19 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: v20.1.6 + hooks: + - id: clang-format + types_or: [c++, c] +- repo: https://github.com/psf/black + rev: 25.1.0 + hooks: + - id: black diff --git a/CMakeLists.txt b/CMakeLists.txt index 37ef3631e..890d23ee3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,10 @@ cmake_minimum_required (VERSION 3.22) +enable_testing() set (CMAKE_EXPORT_COMPILE_COMMANDS ON) project (task - VERSION 3.0.1 + VERSION 3.4.1 DESCRIPTION "Taskwarrior - a command-line TODO list manager" HOMEPAGE_URL https://taskwarrior.org/) @@ -24,19 +25,19 @@ if (ENABLE_WASM) set(CMAKE_EXECUTABLE_SUFFIX ".js") endif (ENABLE_WASM) -message ("-- Looking for libshared") -if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) - message ("-- Found libshared") +message ("-- Looking for git submodules") +if (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion) + message ("-- Found git submodules") else (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) - message ("-- Cloning libshared") + message ("-- Cloning git submodules") execute_process (COMMAND git submodule update --init WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) -endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src) +endif (EXISTS ${CMAKE_SOURCE_DIR}/src/libshared/src AND EXISTS ${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion) message ("-- Looking for SHA1 references") if (EXISTS ${CMAKE_SOURCE_DIR}/.git/index) set (HAVE_COMMIT true) - execute_process (COMMAND git log -1 --pretty=format:%h + execute_process (COMMAND git log -1 --pretty=format:%h --no-show-signature WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE COMMIT) configure_file ( ${CMAKE_SOURCE_DIR}/commit.h.in @@ -66,7 +67,6 @@ SET (TASK_BINDIR bin CACHE STRING "Installation directory for the bi # rust libs require these set (TASK_LIBRARIES dl pthread) -check_function_exists (timegm HAVE_TIMEGM) check_function_exists (get_current_dir_name HAVE_GET_CURRENT_DIR_NAME) check_function_exists (wordexp HAVE_WORDEXP) @@ -141,43 +141,26 @@ configure_file ( add_subdirectory (src) add_subdirectory (src/commands) -add_subdirectory (src/tc) +add_subdirectory (src/taskchampion-cpp) add_subdirectory (src/columns) add_subdirectory (doc) add_subdirectory (scripts) if (EXISTS ${CMAKE_SOURCE_DIR}/test) add_subdirectory (test EXCLUDE_FROM_ALL) endif (EXISTS ${CMAKE_SOURCE_DIR}/test) -if (EXISTS performance) +if (EXISTS ${CMAKE_SOURCE_DIR}/performance) add_subdirectory (performance EXCLUDE_FROM_ALL) -endif (EXISTS performance) +endif (EXISTS ${CMAKE_SOURCE_DIR}/performance) set (doc_FILES ChangeLog README.md INSTALL AUTHORS COPYING LICENSE) foreach (doc_FILE ${doc_FILES}) install (FILES ${doc_FILE} DESTINATION ${TASK_DOCDIR}) endforeach (doc_FILE) -add_custom_command(OUTPUT run-review - COMMAND docker build -q --build-arg PR=$(PR) --build-arg LIBPR=$(LIBPR) -t taskwarrior-review:$(PR)s$(LIBPR) - < scripts/review-dockerfile - COMMAND docker run --rm --memory 1g --hostname pr-$(PR)s$(LIBPR) -it taskwarrior-review:$(PR)s$(LIBPR) bash || : -) -add_custom_target(review DEPENDS run-review) - -add_custom_command(OUTPUT run-reproduce - COMMAND docker build -q --build-arg RELEASE=$(RELEASE) -t taskwarrior-reproduce:$(RELEASE) - < scripts/reproduce-dockerfile - COMMAND docker run --rm --memory 1g --hostname tw-$(RELEASE) -it taskwarrior-reproduce:$(RELEASE) bash || : -) -add_custom_target(reproduce DEPENDS run-reproduce) - -add_custom_command(OUTPUT show-problems - COMMAND cd test && ./problems -) -add_custom_target(problems DEPENDS show-problems) - # --- set (CPACK_SOURCE_GENERATOR "TGZ") set (CPACK_SOURCE_PACKAGE_FILE_NAME ${PACKAGE_NAME}-${PACKAGE_VERSION}) -set (CPACK_SOURCE_IGNORE_FILES "build" "test" "misc/*" "performance" "swp$" "src/lex$" "task-.*.tar.gz" +set (CPACK_SOURCE_IGNORE_FILES "build" "target" "test" "misc/*" "performance" "swp$" "src/lex$" "task-.*.tar.gz" "commit.h" "cmake.h$" "\\\\.gitmodules" "src/libshared/\\\\.git" ".github/" ".*\\\\.gitignore$" "docker-compose.yml" "\\\\.git/") include (CPack) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 120000 index 000000000..a5e92ccc7 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1 @@ +doc/devel/contrib/development.md \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8d742b235..299662c96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,39 +4,52 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -47,16 +60,22 @@ dependencies = [ ] [[package]] -name = "anyhow" -version = "1.0.66" +name = "anstyle" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -65,91 +84,540 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] name = "async-trait" -version = "0.1.69" +version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2d0f03b3640e3a630367e40c468cb7f309529c708ed1d88597047b0e7c6ef7" +checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "aws-config" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a84fe2c5e9965fba0fbc2001db252f1d57527d82a905cca85127df227bca748" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.3.1", + "ring", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4471bef4c22a06d2c7a1b6492493d3fdf24a805323109d6874f9c94d5906ac14" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + +[[package]] +name = "aws-lc-rs" +version = "1.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dabb68eb3a7aa08b46fddfd59a3d55c978243557a90ab804769f7e20e67d2b01" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbe221bbf523b625a4dd8585c7f38166e31167ec2ca98051dbcb4c3b6e825d2" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "aws-runtime" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aff45ffe35196e593ea3b9dd65b320e51e2dda95aff4390bc459e461d09c6ad" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.79.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f63ba8f5fca32061c7d62d866ef65470edde38d4c5f8a0ebb8ff40a0521e1c" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "lru", + "once_cell", + "percent-encoding", + "regex-lite", + "sha2", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d5330ad4e8a1ff49e9f26b738611caa72b105c41d41733801d1a36e8f9de936" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7956b1a85d49082347a7d17daa2e32df191f3e23c03d47294b99f95413026a78" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065c533fbe6f84962af33fcf02b0350b7c1f79285baab5924615d2be3b232855" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "http 0.2.12", + "once_cell", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d03c3c05ff80d54ff860fe38c726f6f494c639ae975203a101335f223386db" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint 0.5.5", + "form_urlencoded", + "hex", + "hmac", + "http 0.2.12", + "http 1.3.1", + "once_cell", + "p256", + "percent-encoding", + "ring", + "sha2", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.63.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65d21e1ba6f2cdec92044f904356a19f5ad86961acf015741106cdfafd747c0" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc32c", + "crc32fast", + "crc64fast-nvme", + "hex", + "http 0.2.12", + "http-body 0.4.6", + "md-5", + "pin-project-lite", + "sha1", + "sha2", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c45d3dddac16c5c59d553ece225a88870cf81b7b813c9cc17b78cf4685eac7a" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.62.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5949124d11e538ca21142d1fba61ab0a2a2c1bc3ed323cdb3e4b878bfb83166" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "once_cell", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0497ef5d53065b7cd6a35e9c1654bd1fefeae5c52900d91d1b188b0af0f29324" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.4.8", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.6.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.5", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92144e45819cae7dc62af23eac5a038a58aa544432d2102609654376a900bd07" +dependencies = [ + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6328865e36c6fd970094ead6b05efd047d3a80ec5fc3be5e743910da9f2ebf8" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "once_cell", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da37cf5d57011cb1753456518ec76e31691f1f474b73934a284eb2a1c76510f" +dependencies = [ + "aws-smithy-async", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.3.1", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836155caafba616c0ff9b07944324785de2ab016141c3550bd1c07882f8cee8f" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.3.1", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0b0166827aa700d3dc519f72f8b3a91c35d0b8d042dc5d643a91e6f80648fc" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3873f8deed8927ce8d04487630dc9ff73193bab64742a61d050e57a68dec4125" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "rustc_version", + "tracing", +] [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.1", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] -name = "base64" -version = "0.13.1" +name = "base16ct" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] name = "base64" -version = "0.21.0" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] [[package]] name = "base64ct" -version = "1.6.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "bb97d56060ee67d285efb8001fec9d2a4c710c32efd2e14b5cbb5ba71930fc2d" [[package]] -name = "bit-set" -version = "0.5.2" +name = "bindgen" +version = "0.69.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" dependencies = [ - "bit-vec", + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", ] -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" -version = "1.3.2" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487f1e0fcbe47deb8b0574e646def1c903389d95241dd1bbcc6ce4a715dfc0c1" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -162,9 +630,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" @@ -174,15 +642,39 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] [[package]] name = "cc" -version = "1.0.73" +version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -191,19 +683,80 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "chrono" -version = "0.4.22" +name = "cfg_aliases" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", - "time 0.1.43", "wasm-bindgen", - "winapi", + "windows-link", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +dependencies = [ + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", ] [[package]] @@ -213,29 +766,104 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "core-foundation-sys" -version = "0.8.3" +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] -name = "crc32fast" -version = "1.3.2" +name = "crc" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32c" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] +[[package]] +name = "crc64fast-nvme" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4955638f00a809894c947f85a024020a20815b65a5eea633798ea7924edab2b3" +dependencies = [ + "crc", +] + +[[package]] +name = "crypto-bigint" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -247,10 +875,79 @@ dependencies = [ ] [[package]] -name = "der" -version = "0.7.8" +name = "cxx" +version = "1.0.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffa369a668c8af7dbf8b5e56c9f744fbd399949ed171606040001947de40b1c" +checksum = "4615b1496a78e2c624b792d982e5d2152db2e5b53d401605776ec819e50891ce" +dependencies = [ + "cc", + "cxxbridge-cmd", + "cxxbridge-flags", + "cxxbridge-macro", + "foldhash", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147bafb46dc3ad9d24717723751371147373ffa8cf5f816e0031e34d6998b339" +dependencies = [ + "cc", + "codespan-reporting", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-cmd" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07d4631e3095af42e8de3c73ee2b7d49fe541578ccd9f6b19920ac3c5fef528c" +dependencies = [ + "clap", + "codespan-reporting", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05c4a04df781bb50f52a16cfd7c580d0d904af8e7c411678be52d84ed3416ab" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93be4a484f2b719c2cb56ab5f06e05377987477c7b3bf7a1cf28a266ec8cfaa1" +dependencies = [ + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "der" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a467a65c5e759bce6e65eaf91cc29f466cdc57cb65777bd646872a8a1fd4de" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", @@ -258,62 +955,111 @@ dependencies = [ ] [[package]] -name = "diff" -version = "0.1.12" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ecdsa" +version = "0.14.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" +dependencies = [ + "der 0.6.1", + "elliptic-curve", + "rfc6979", + "signature", ] [[package]] name = "either" -version = "1.8.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" +dependencies = [ + "base16ct", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest", + "ff", + "generic-array", + "group", + "pkcs8 0.9.0", + "rand_core", + "sec1", + "subtle", + "zeroize", +] [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] -name = "errno" -version = "0.3.1" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "errno" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "cc", "libc", + "windows-sys 0.59.0", ] [[package]] name = "fallible-iterator" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" @@ -323,44 +1069,28 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "1.7.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "ffizz-header" -version = "0.5.0" +name = "ff" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a1a52e9f00aa4c639059d977e1289a13963117d8e60ccb25e86cca2aab98538" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ - "ffizz-macros", - "itertools", - "linkme", -] - -[[package]] -name = "ffizz-macros" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af208cea557bab3ec4f05fb0c26460f1c61fdb204f3738f471b6b1ecd58d7a04" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn 1.0.104", + "rand_core", + "subtle", ] [[package]] name = "flate2" -version = "1.0.24" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", - "miniz_oxide 0.5.1", + "miniz_oxide", ] [[package]] @@ -369,6 +1099,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -379,89 +1115,61 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.25" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-executor" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -475,9 +1183,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -485,29 +1193,49 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "google-cloud-auth" -version = "0.13.0" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af1087f1fbd2dd3f58c17c7574ddd99cd61cbbbc2c4dc81114b8687209b196cb" +checksum = "e57a13fbacc5e9c41ded3ad8d0373175a6b7a6ad430d99e89d314ac121b7ab06" dependencies = [ "async-trait", - "base64 0.21.0", + "base64 0.21.7", "google-cloud-metadata", "google-cloud-token", "home", @@ -515,8 +1243,8 @@ dependencies = [ "reqwest", "serde", "serde_json", - "thiserror", - "time 0.3.20", + "thiserror 1.0.69", + "time", "tokio", "tracing", "urlencoding", @@ -524,24 +1252,25 @@ dependencies = [ [[package]] name = "google-cloud-metadata" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc279bfb50487d7bcd900e8688406475fc750fe474a835b2ab9ade9eb1fc90e2" +checksum = "d901aeb453fd80e51d64df4ee005014f6cf39f2d736dd64f7239c132d9d39a6a" dependencies = [ "reqwest", - "thiserror", + "thiserror 1.0.69", "tokio", ] [[package]] name = "google-cloud-storage" -version = "0.15.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac04b29849ebdeb9fb008988cc1c4d1f0c9d121b4c7f1ddeb8061df124580e93" +checksum = "e81dff54dbfa83705c896179ecaa4f384bfbfac90f3b637f38541443275b8a3f" dependencies = [ + "anyhow", "async-stream", "async-trait", - "base64 0.21.0", + "base64 0.21.7", "bytes", "futures-util", "google-cloud-auth", @@ -550,15 +1279,16 @@ dependencies = [ "hex", "once_cell", "percent-encoding", - "pkcs8", + "pkcs8 0.10.2", "regex", "reqwest", - "ring 0.17.3", + "reqwest-middleware", + "ring", "serde", "serde_json", "sha2", - "thiserror", - "time 0.3.20", + "thiserror 1.0.69", + "time", "tokio", "tracing", "url", @@ -574,17 +1304,47 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.17" +name = "group" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66b91535aa35fea1523ad1b86cb6b53c28e0ae566ba4a460f4457e936cad7c6f" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -594,48 +1354,38 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" - -[[package]] -name = "hashbrown" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", ] [[package]] -name = "hashlink" -version = "0.8.0" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "hashbrown 0.12.2", + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -644,19 +1394,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] -name = "home" -version = "0.5.9" +name = "hmac" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "windows-sys 0.52.0", + "digest", +] + +[[package]] +name = "home" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" +dependencies = [ + "windows-sys 0.59.0", ] [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -670,46 +1440,89 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.3.1", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2", "tokio", "tower-service", "tracing", "want", ] +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.8", + "http 1.3.1", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -717,119 +1530,275 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", - "rustls", + "http 0.2.12", + "hyper 0.14.32", + "log", + "rustls 0.21.12", + "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http 1.3.1", + "hyper 1.6.0", + "hyper-util", + "rustls 0.23.23", + "rustls-native-certs 0.8.1", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.2", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", - "once_cell", "wasm-bindgen", - "winapi", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "idna" -version = "0.5.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "1.8.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ - "autocfg", - "hashbrown 0.11.2", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "integration-tests" -version = "0.4.1" -dependencies = [ - "anyhow", - "cc", - "lazy_static", - "pretty_assertions", - "taskchampion", - "taskchampion-lib", - "tempfile", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown 0.15.2", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" -version = "0.10.5" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "jsonwebtoken" -version = "8.3.0" +version = "9.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", + "js-sys", "pem", - "ring 0.16.20", + "ring", "serde", "serde_json", "simple_asn1", @@ -837,27 +1806,37 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] -name = "libm" -version = "0.2.6" +name = "libloading" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] [[package]] name = "libsqlite3-sys" -version = "0.26.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -865,36 +1844,31 @@ dependencies = [ ] [[package]] -name = "linkme" -version = "0.3.8" +name = "link-cplusplus" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc2b30967da1bcca8f15aa741f2b949a315ef0eabd0ef630a5a0643d7a45260" +checksum = "4a6f6da007f968f9def0d65a05b187e2960183de70c160204ecfccf0ee330212" dependencies = [ - "linkme-impl", -] - -[[package]] -name = "linkme-impl" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a440f823b734f5a90d7cc2850a2254611092e88fa13fb1948556858ce2d35d2a" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.104", + "cc", ] [[package]] name = "linux-raw-sys" -version = "0.3.8" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -902,125 +1876,164 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.2", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", + "digest", ] [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] -name = "miniz_oxide" -version = "0.5.1" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" -dependencies = [ - "adler", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", - "windows-sys 0.48.0", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", ] [[package]] name = "num-bigint" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi 0.1.19", - "libc", ] [[package]] name = "object" -version = "0.32.1" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + +[[package]] +name = "p256" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" +dependencies = [ + "ecdsa", + "elliptic-curve", + "sha2", +] [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1028,24 +2041,25 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.13", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.6", ] [[package]] name = "pem" -version = "1.1.1" +version = "3.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", + "serde", ] [[package]] @@ -1065,9 +2079,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1075,78 +2089,123 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" +dependencies = [ + "der 0.6.1", + "spki 0.6.0", +] + [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.9", + "spki 0.7.3", ] [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy 0.8.23", +] [[package]] -name = "pretty_assertions" -version = "1.4.0" +name = "prettyplease" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66" +checksum = "f1ccf34da56fc294e7d4ccf69a85992b7dfb826b7cf57bac6a70bba3494cc08a" dependencies = [ - "diff", - "yansi", + "proc-macro2", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.60" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] -name = "proptest" -version = "1.4.0" +name = "quinn" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.0.2", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.1", + "rustls 0.23.23", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", ] [[package]] -name = "quick-error" -version = "1.2.3" +name = "quinn-proto" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom 0.2.15", + "rand", + "ring", + "rustc-hash 2.1.1", + "rustls 0.23.23", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e46f3055866785f6b92bc6164b76be02ca8f2eb4b002c0354b28cf4c119e5944" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] [[package]] name = "quote" -version = "1.0.28" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -1174,45 +2233,27 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", + "getrandom 0.2.15", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1222,9 +2263,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1232,27 +2273,34 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.2" +name = "regex-lite" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a" + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.11.18" +version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" +checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "hyper 1.6.0", + "hyper-rustls 0.27.5", + "hyper-util", "ipnet", "js-sys", "log", @@ -1261,86 +2309,75 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", + "quinn", + "rustls 0.23.23", + "rustls-pemfile 2.2.0", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.2", "tokio-util", + "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.22.6", - "winreg", + "webpki-roots", + "windows-registry", +] + +[[package]] +name = "reqwest-middleware" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64e8975513bd9a7a43aad01030e79b3498e05db14e9d945df6483e8cf9b8c4c4" +dependencies = [ + "anyhow", + "async-trait", + "http 1.3.1", + "reqwest", + "serde", + "thiserror 1.0.69", + "tower-service", +] + +[[package]] +name = "rfc6979" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" +dependencies = [ + "crypto-bigint 0.4.9", + "hmac", + "zeroize", ] [[package]] name = "ring" -version = "0.16.20" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babe80d5c16becf6594aa32ad2be8fe08498e7ae60b77de8df700e67f191d7e" -dependencies = [ - "cc", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.48.0", -] - -[[package]] -name = "rstest" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de1bb486a691878cd320c2f0d319ba91eeaa2e894066d8b5f8f117c000e9d962" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290ca1a1c8ca7edb7c3283bd44dc35dd54fdec6253a3912e201ba1072018fca8" -dependencies = [ "cfg-if", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.104", - "unicode-ident", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", ] [[package]] name = "rusqlite" -version = "0.29.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" dependencies = [ - "bitflags 2.0.2", + "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -1350,52 +2387,134 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.37.25" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4eb579851244c2c03e7c24f501c3432bed80b8f720af1d6e5b0e0f01555a035" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "rustls" -version = "0.21.11" +version = "0.21.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.3", - "rustls-webpki", + "ring", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" +dependencies = [ + "openssl-probe", + "rustls-pemfile 1.0.4", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.2.0", + "rustls-pki-types", + "schannel", + "security-framework 2.11.1", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.0", + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +dependencies = [ + "web-time", ] [[package]] @@ -1404,83 +2523,149 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.3", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error", - "tempfile", - "wait-timeout", -] +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scratch" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f6280af86e5f559536da57a45ebc84948833b3bee313a7dd25232e09c878a52" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" +dependencies = [ + "base16ct", + "der 0.6.1", + "generic-array", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] name = "semver" -version = "1.0.9" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.147" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1498,10 +2683,10 @@ dependencies = [ ] [[package]] -name = "sha2" +name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -1509,61 +2694,88 @@ dependencies = [ ] [[package]] -name = "simple_asn1" -version = "0.6.2" +name = "sha2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" dependencies = [ "num-bigint", "num-traits", - "thiserror", - "time 0.3.20", + "thiserror 2.0.12", + "time", ] [[package]] name = "slab" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "socket2" -version = "0.5.5" +name = "spki" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ - "libc", - "windows-sys 0.48.0", + "base64ct", + "der 0.6.1", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spki" version = "0.7.3" @@ -1571,33 +2783,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.9", ] [[package]] -name = "strum" -version = "0.25.0" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "strum_macros" -version = "0.25.3" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", - "syn 2.0.18", + "syn", ] [[package]] -name = "syn" -version = "1.0.104" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae548ec36cf198c0ef7710d3c230987c2d6d7bd98ad6edc0274462724c585ce" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -1605,37 +2835,47 @@ dependencies = [ ] [[package]] -name = "syn" -version = "2.0.18" +name = "sync_wrapper" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] [[package]] name = "taskchampion" -version = "0.4.1" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830bb062bb2d89bdee0063d7c02d1e24ee0a1702c683f394eb0520fb88dc4a5c" dependencies = [ "anyhow", + "aws-config", + "aws-credential-types", + "aws-sdk-s3", "byteorder", "chrono", "flate2", "google-cloud-storage", "log", - "pretty_assertions", - "proptest", - "ring 0.17.3", - "rstest", + "ring", "rusqlite", "serde", "serde_json", "strum", "strum_macros", - "tempfile", - "thiserror", + "thiserror 2.0.12", "tokio", "ureq", "url", @@ -1646,64 +2886,70 @@ dependencies = [ name = "taskchampion-lib" version = "0.1.0" dependencies = [ - "anyhow", - "ffizz-header", - "libc", - "pretty_assertions", + "cxx", + "cxx-build", "taskchampion", ] [[package]] -name = "tempfile" -version = "3.6.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "autocfg", - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.48.0", + "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "time" -version = "0.1.43" +version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ + "deranged", "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -1711,61 +2957,72 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ + "num-conv", "time-core", ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", - "socket2 0.5.5", + "signal-hook-registry", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] @@ -1774,37 +3031,66 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls 0.23.23", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] -name = "tower-service" -version = "0.3.2" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -1812,22 +3098,22 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.18", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -1838,51 +3124,27 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.15.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] -name = "unicode-normalization" -version = "0.1.22" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "untrusted" @@ -1892,25 +3154,26 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7830e33f6e25723d41a63f77e434159dad02919f18f55a512b5f16f3b1d77138" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "flate2", "log", "once_cell", - "rustls", - "rustls-webpki", + "rustls 0.23.23", + "rustls-native-certs 0.7.3", + "rustls-pki-types", "url", - "webpki-roots 0.25.2", + "webpki-roots", ] [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -1924,12 +3187,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] -name = "uuid" -version = "1.8.0" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0f540e3240398cce6128b64ba83fdbdd86129c16a3aa1a3a252efd66eb3d587" dependencies = [ - "getrandom", + "getrandom 0.3.1", "serde", ] @@ -1941,18 +3216,15 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] -name = "wait-timeout" -version = "0.2.0" +name = "vsimd" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" [[package]] name = "want" @@ -1970,47 +3242,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.82" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 1.0.104", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.32" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2018,28 +3301,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 1.0.104", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" -version = "0.2.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -2050,77 +3336,96 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki" -version = "0.22.4" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "ring 0.17.3", - "untrusted 0.9.0", + "js-sys", + "wasm-bindgen", ] [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ - "webpki", + "rustls-pki-types", ] [[package]] -name = "webpki-roots" -version = "0.25.2" +name = "which" +version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "either", + "home", + "once_cell", + "rustix", ] [[package]] -name = "winapi-i686-pc-windows-gnu" +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-link" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" + +[[package]] +name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ - "windows-targets 0.42.2", + "windows-result", + "windows-strings", + "windows-targets 0.53.0", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-result" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" dependencies = [ - "windows-targets 0.48.0", + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", ] [[package]] @@ -2129,206 +3434,282 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] -name = "windows_i686_gnu" -version = "0.52.0" +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" +name = "wit-bindgen-rt" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "winreg" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ - "winapi", + "bitflags", ] [[package]] -name = "xtask" -version = "0.4.1" +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ - "anyhow", - "regex", - "taskchampion-lib", + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", ] [[package]] -name = "yansi" -version = "0.5.1" +name = "yoke-derive" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" +dependencies = [ + "zerocopy-derive 0.8.23", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index efe0c179d..1e4fec553 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,42 +1,7 @@ [workspace] members = [ - "taskchampion/taskchampion", - "taskchampion/lib", - "taskchampion/integration-tests", - "taskchampion/xtask", + "src/taskchampion-cpp", ] resolver = "2" - -# src/tc/rust is just part of the TW build and not a public crate -exclude = [ "src/tc/rust" ] - -# All Rust dependencies are defined here, and then referenced by the -# Cargo.toml's in the members with `foo.workspace = true`. -[workspace.dependencies] -anyhow = "1.0" -byteorder = "1.5" -cc = "1.0.73" -chrono = { version = "^0.4.22", features = ["serde"] } -ffizz-header = "0.5" -flate2 = "1" -google-cloud-storage = { version = "0.15.0", default-features = false, features = ["rustls-tls", "auth"] } -lazy_static = "1" -libc = "0.2.136" -log = "^0.4.17" -pretty_assertions = "1" -proptest = "^1.4.0" -ring = "0.17" -rstest = "0.17" -rusqlite = { version = "0.29", features = ["bundled"] } -serde_json = "^1.0" -serde = { version = "^1.0.147", features = ["derive"] } -strum = "0.25" -strum_macros = "0.25" -tempfile = "3" -tokio = { version = "1", features = ["rt-multi-thread"] } -thiserror = "1.0" -ureq = { version = "^2.9.0", features = ["tls"] } -uuid = { version = "^1.8.0", features = ["serde", "v4"] } -url = { version = "2" } diff --git a/ChangeLog b/ChangeLog index 9f56ea666..5d372307b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,111 @@ ------ current release --------------------------- +3.4.1 - + +- The nagging to read `task news` has been fixed. + +------ old releases ------------------------------ + +3.4.0 - + +- Where possible, the task DB is now opened in read-only mode, which improves + performance. This is the case for reports (task lists) only when the `gc` + config is false. +- An updated version of corrosion fixes build errors trying to find the Rust + toolchain. + +Thanks to the following people for contributions to this release: + + - Dustin J. Mitchell + - Kalle Kietäväinen + - Karl + - Matthew + - Tejada-Omar + - Tobias Predel + - Yong Li + - jrmarino + +3.3.0 - + +- Sync now supports AWS S3 as a backend. +- A new `task import-v2` command allows importing Taskwarrior-2.x + data files directly. + +Thanks to the following people for contributions to this release: + + - Chongyun Lee + - David Tolnay + - Dustin J. Mitchell + - Felix Schurk + - geoffpaulsen + - Kalle Kietäväinen + - Kursat Aktas + - Scott Mcdermott + - Thomas Lauf + +3.2.0 - + + - Support for the journal in `task info` has been restored (#3671) and the + task info output no longer contains `tag_` values (#3619). + - The `rc.weekstart` value now affects calculation of week numbers in + expressions like `2013-W49` (#3654). + - Build-time flag `ENABLE_TLS_NATIVE_ROOTS` will cause `task sync` to use the + system TLS roots instead of its built-in roots to authenticate the server (#3660). + - The output from `task undo` is now more human-readable. The `undo.style` + configuraiton option, which has had no effect since 3.0.0, is now removed (3672). + - Fetching pending tasks is now more efficient (#3661). + +Thanks to the following people for contributions to this release: + + - Denis Zh. + - Dustin J. Mitchell + - Fredrik Lanker + - Gagan Nagaraj + - Jan Christian Grünhage + - Scott Mcdermott + - Thomas Lauf + - Tobias Predel + +3.1.0 - + + - Support for `task purge` has been restored, and new support added for automatically + expiring old tasks. (#3540, #3546, #3556) + - `task news` is now better behaved, and can be completely disabled. + - Multiple imports of the same UUID will now generate a warning. (#3560) + - The `sync.server.url` config replaces `sync.server.origin` and allows a URL + containing a path. (#3423) + - The new `bubblegum-256.theme` has improved legibility and contrast over + others. (#3505) + - Warnings regarding `.data` files are only show for reports. (#3473) + - Inherited urgency is correctly calculated to make parents more urgent than + children (#2941) + - Task completion commands no longer trigger hooks (#3133) + +Thanks to the following people for contributions to this release: + + - Adrian Galilea + - Adrian Sadłocha + - Andonome + - Christian Clauss + - Dominik Rehák + - Dustin J. Mitchell + - Felix Schurk + - Hector Dearman + - Joseph Coffa + - koleesch + - Maarten Aertsen + - mattsmida + - Philipp Oberdiek + - Sebastian Carlos + - sleepy_nols + - Steve Dondley + - Will R S Hansen + +3.0.2 - + +- Fix an accidentally-included debug print which polluted output of + reports with the Taskwarrior version (#3389) + 3.0.1 - - Fix an error in creation of the 3.0.0 tarball which caused builds to fail (#3302) @@ -8,8 +114,6 @@ - Fix incorrect task ID of 0 when using hooks (#3339) - Issue a warning if .data files remain (#3321) ------- old releases ------------------------------ - 3.0.0 - - [BREAKING CHANGE] the sync functionality has been rewritten entirely, and @@ -38,7 +142,7 @@ - Akash Shanmugaraj - Andrew Savchenko - - Dathan Bennett + - Dathan Bennett - Dustin Mitchell - dbr/Ben - Felix Schurk @@ -2779,4 +2883,3 @@ regular usage to determine which features were needed or unnecessary.] - Usage. ------ start ----------------------------------- - diff --git a/INSTALL b/INSTALL index 4e885f645..beee2018f 100644 --- a/INSTALL +++ b/INSTALL @@ -21,18 +21,20 @@ You will need a C++ compiler that supports full C++17, which includes: You will need the following libraries: - libuuid (not needed for OSX) +You will need a Rust toolchain of the Minimum Supported Rust Version (MSRV): + - rust 1.81.0 Basic Installation ------------------ Briefly, these shell commands will unpack, build and install Taskwarrior: - $ tar xzf task-X.Y.Z.tar.gz [1] - $ cd task-X.Y.Z [2] - $ cmake -DCMAKE_BUILD_TYPE=release . [3] - $ make [4] - $ sudo make install [5] - $ cd .. ; rm -r task-X.Y.Z [6] + $ tar xzf task-X.Y.Z.tar.gz [1] + $ cd task-X.Y.Z [2] + $ cmake -S . -B build -DCMAKE_BUILD_TYPE=Release . [3] + $ cmake --build build [4] + $ sudo cmake --install build [5] + $ cd .. ; rm -r task-X.Y.Z [6] (see: Uninstallation) These commands are explained below: @@ -87,6 +89,11 @@ get absolute installation directories: CMAKE_INSTALL_PREFIX/TASK_MAN1DIR /usr/local/share/man/man1 CMAKE_INSTALL_PREFIX/TASK_MAN5DIR /usr/local/share/man/man5 +The following variables control aspects of the build process: + + SYSTEM_CORROSION - Use system provided corrosion instead of vendored version + ENABLE_TLS_NATIVE_ROOTS - Use the system's TLS root certificates + Uninstallation -------------- @@ -96,6 +103,13 @@ There is no uninstall option in CMake makefiles. This is a manual process. To uninstall Taskwarrior, remove the files listed in the install_manifest.txt file that was generated when you built Taskwarrior. +```sh +cd task-X.Y.Z +sudo xargs rm < build/install_manifest.txt +``` + +If you want to uninstall this way, you will need to omit step [6] above and +retain the source folder after installation. Taskwarrior Build Notes ----------------------- @@ -108,6 +122,43 @@ If Taskwarrior will not build on your system, first take a look at the Operating System notes below. If this doesn't help, then go to the Troubleshooting section, which includes instructions on how to contact us for help. +Offline Build Notes +------------------- + +It is common for packaging systems (e.g. NixOS, FreeBSD Ports Collection, pkgsrc, etc) +to disable networking during builds. This restriction requires all distribution files +to be prepositioned after checksum verification as a prerequisite for the build. The +following steps have been successful in allowing Taskwarrior to be built in this +environment: + +1. Extract all crates in a known location, e.g. ${WRKDIR}/cargo-crates +This includes crates needed for corrosion (search for Cargo.lock files) + +2. Create .cargo-checksum.json for each crate +For example: +printf '{"package":"%s","files":{}}' $(sha256 -q ${DISTDIR}/rayon-core-1.12.1.tar.gz) \ + > ${WRKDIR}/cargo-crates/rayon-core-1.12.1/.cargo-checksum.json + +3. Create a custom config.toml file +For example, ${WRKDIR}/.cargo/config.toml +[source.cargo] +directory = '${WRKDIR}/cargo-crates' +[source.crates-io] +replace-with = 'cargo' + +4. After running cmake, configure cargo +For example: +cd ${WRKSRC} && ${SETENV} ${MAKE_ENV} ${CARGO_ENV} \ + /usr/local/bin/cargo update \ + --manifest-path ${WRKDIR}/.cargo/config.toml \ + --verbose + +5. Set CARGO_HOME in environment +For example +CARGO_HOME=${WRKDIR}/.cargo + +The build and installation steps should be the same as a standard build +at this point. Operating System Notes ---------------------- diff --git a/LICENSE b/LICENSE index 89dcf89c2..96d1910f0 100644 --- a/LICENSE +++ b/LICENSE @@ -21,4 +21,3 @@ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md index f1ae0998f..f2bc94720 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,11 @@
[![GitHub Actions build status](https://github.com/GothenburgBitFactory/taskwarrior/workflows/tests/badge.svg?branch=develop)](https://github.com/GothenburgBitFactory/taskwarrior/actions) +[![Coverage Status](https://coveralls.io/repos/github/GothenburgBitFactory/taskwarrior/badge.svg?branch=develop)](https://coveralls.io/github/GothenburgBitFactory/taskwarrior?branch=develop) [![Release](https://img.shields.io/github/v/release/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest) [![Release date](https://img.shields.io/github/release-date/GothenburgBitFactory/taskwarrior)](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest) [![GitHub Sponsors](https://img.shields.io/github/sponsors/GothenburgBitFactory?color=green)](https://github.com/sponsors/GothenburgBitFactory/) +[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20Taskwarrior%20Guru-006BFF)](https://gurubase.io/g/taskwarrior)
@@ -38,11 +40,9 @@ The [online documentation](https://taskwarrior.org/docs), downloads, news and more are available on our website, [taskwarrior.org](https://taskwarrior.org). ## Community -[![Twitter](https://img.shields.io/twitter/follow/taskwarrior?style=social)](https://twitter.com/taskwarrior) -[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/taskwarrior?style=social)](https://reddit.com/r/taskwarrior/) -[![Libera.chat](https://img.shields.io/badge/IRC%20libera.chat-online-green)](https://web.libera.chat/#taskwarrior) [![Discord](https://img.shields.io/discord/796949983734661191?label=discord)](https://discord.gg/eRXEHk8w62) [![Github discussions](https://img.shields.io/github/discussions/GothenburgBitFactory/taskwarrior?label=GitHub%20discussions)](https://github.com/GothenburgBitFactory/taskwarrior/discussions) +[![Reddit](https://img.shields.io/reddit/subreddit-subscribers/taskwarrior?style=social)](https://reddit.com/r/taskwarrior/) Taskwarrior has a lively community on many places on the internet. diff --git a/RELEASING.md b/RELEASING.md new file mode 120000 index 000000000..51170bf7a --- /dev/null +++ b/RELEASING.md @@ -0,0 +1 @@ +doc/devel/contrib/releasing.md \ No newline at end of file diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..203bc5e64 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security + +To report a vulnerability, please contact Dustin via signal, [`djmitche.78`](https://signal.me/#eu/2T98jpkMAzvFL2wg3OkZnNrfhk1DFfu6eqkMEPqcAuCsLZPVk39A67rp4khmrMNF). +Initial response is expected within ~48h. + +We kindly ask to follow the responsible disclosure model and refrain from sharing information until: + +1. Vulnerabilities are patched in Taskwarrior + 60 days to coordinate with distributions. +2. 90 days since the vulnerability is disclosed to us. + +We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. + +We will assist with obtaining CVE and acknowledge the vulnerabilities reported. diff --git a/cmake.h.in b/cmake.h.in index 6586a7f03..25c0d6acc 100644 --- a/cmake.h.in +++ b/cmake.h.in @@ -41,9 +41,6 @@ /* Found tm_gmtoff */ #cmakedefine HAVE_TM_GMTOFF -/* Found timegm */ -#cmakedefine HAVE_TIMEGM - /* Found st.st_birthtime struct member */ #cmakedefine HAVE_ST_BIRTHTIME @@ -58,4 +55,3 @@ /* Undefine this to eliminate the execute command */ #define HAVE_EXECUTE 1 - diff --git a/cmake/CXXSniffer.cmake b/cmake/CXXSniffer.cmake index 07c06cdae..ef1ec6511 100644 --- a/cmake/CXXSniffer.cmake +++ b/cmake/CXXSniffer.cmake @@ -6,7 +6,8 @@ include (CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++17" _HAS_CXX17) if (_HAS_CXX17) - set (_CXX14_FLAGS "-std=c++17") + set (CMAKE_CXX_STANDARD 17) + set (CMAKE_CXX_EXTENSIONS OFF) else (_HAS_CXX17) message (FATAL_ERROR "C++17 support missing. Try upgrading your C++ compiler. If you have a good reason for using an outdated compiler, please let us know at support@gothenburgbitfactory.org.") endif (_HAS_CXX17) @@ -32,7 +33,7 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "GNU") set (GNUHURD true) elseif (${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN") set (CYGWIN true) - set (_CXX14_FLAGS "-std=gnu++17") + set (CMAKE_CXX_EXTENSIONS ON) else (${CMAKE_SYSTEM_NAME} MATCHES "Linux") set (UNKNOWN true) endif (${CMAKE_SYSTEM_NAME} MATCHES "Linux") diff --git a/doc/devel/README.md b/doc/devel/README.md index 7a09621b5..4f55544e3 100644 --- a/doc/devel/README.md +++ b/doc/devel/README.md @@ -11,10 +11,6 @@ For all other documenation, see https://taskwarrior.org. As of the 3.0 release, Taskwarrior uses TaskChampion to manage task data. Find documentation of TaskChampion here: - * [TaskChampion README](../../taskchampion) - * [TaskChampion CONTRIBUTING guide](../../taskchampion/CONTRIBUTING.md) - * [TaskChampion Book](../../taskchampion/docs/src/SUMMARY.md) + * [TaskChampion Repository](https://github.com/GothenburgBitFactory/taskchampion/) + * [TaskChampion Book](https://gothenburgbitfactory.github.io/taskchampion/) * [TaskChampion API Documentation](https://docs.rs/taskchampion) - -TaskChampion will [become its own -project](https://github.com/GothenburgBitFactory/taskwarrior/issues/3209) soon. diff --git a/doc/devel/contrib/coding_style.md b/doc/devel/contrib/coding_style.md index 2077bc6b3..7f30d65fb 100644 --- a/doc/devel/contrib/coding_style.md +++ b/doc/devel/contrib/coding_style.md @@ -1,30 +1,18 @@ # Coding Style -The coding style used for the Taskwarrior, Taskserver, and other codebases is deliberately kept simple and a little vague. -This is because there are many languages involved (C++, C, Python, sh, bash, HTML, troff and more), and specіfying those would be a major effort that detracts from the main focus which is improving the software. +The coding style used for the Taskwarrior is based on the [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html), with small modifications in the line length. -Instead, the general guideline is simply this: +# Automatic formatting -Make all changes and additions such that they blend in perfectly with the surrounding code, so it looks like only one person worked on the source, and that person is rigidly consistent. +In order to have consistancy and automatic formatting [pre-commit](https://pre-commit.com) is used to apply [clang-format](https://clang.llvm.org/docs/ClangFormat.html) and [black](https://github.com/psf/black) are used. +In order to set them up locally please run: +```python +pip install pre-commit +pre-commit install +``` -To be a little more explicit: - -## C++ - -- All functionality in C++17 is allowed. - -- Indent C++ code using two spaces, no tabs - -- Surround operators and expression terms with a space. - This includes a space between a function name and its list of arguments. - -- No cuddled braces - -- Class names are capitalized, variable names are not - -## Python - -Follow [PEP8](https://www.python.org/dev/peps/pep-0008/) as much as possible. +For more information refer to the [quick-start of pre-commit](https://pre-commit.com/#quick-start). +The setup is also included in the CI, hence if one can not install it automatically then the CI will take care for it in the PR. ## Rust diff --git a/doc/devel/contrib/development.md b/doc/devel/contrib/development.md index ff6ff5d98..cfc6bce28 100644 --- a/doc/devel/contrib/development.md +++ b/doc/devel/contrib/development.md @@ -1,21 +1,15 @@ # Developing Taskwarrior -The following describes the process for developing Taskwarrior. If you are only -changing TaskChampion (Rust code), you can simply treat it like any other Rust -project: modify the source under `taskchampion/` and use `cargo test` to run -the TaskChampion tests. - -See the [TaskChampion CONTRIBUTING guide](../../../taskchampion/CONTRIBUTING.md) for more. - ## Satisfy the Requirements: - * CMake 3.0 or later + * CMake 3.22 or later * gcc 7.0 or later, clang 6.0 or later, or a compiler with full C++17 support * libuuid (if not on macOS) * Rust 1.64.0 or higher (hint: use https://rustup.rs/ instead of using your system's package manager) ## Install Optional Dependencies: * python 3 (for running the test suite) + * pre-commit (for applying formatting changes locally) * clangd or ccls (for C++ integration in many editors) * rust-analyzer (for Rust integration in many editors) @@ -56,24 +50,57 @@ cmake --build build-clang ``` ## Run the Test Suite: -First switch to the test directory: +For running the test suite [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) is used. +Before one can run the test suite the `task_executable` must be built. +After that also the `test_runner` target must be build, which can be done over: +```sh +cmake --build build --target test_runner +``` +Again you may also use the `-j ` option for parallel builds. +Now one can invoke `ctest` to run the tests. +```sh +ctest --test-dir build ``` - $ cd build/test +This would run all the test in serial and might take some time. + +### Running tests in parallel +```sh +ctest --test-dir build -j ``` -Then you can run all tests, showing details, with + +Further it is adviced to add the `--output-on-failure` option to `ctest`, to recieve a verbose output if a test is failing as well as the `--rerun-failed` flag, to invoke in subsequent runs only the failed ones. + +### Running specific tests +For this case one can use the `-R ` or `--tests-regex ` option to run only the tests matching the regular expression. +Running only the `cpp` tests can then be achieved over +```sh +ctest --test-dir build -R cpp ``` - $ make VERBOSE=1 +or running the `variant_*` tests +```sh +ctest --test-dir build -R variant ``` -Alternately, run the tests with the details hidden in `all.log`: -``` - $ ./run_all -``` -Either way, you can get a summary of any test failures with: -``` - $ ./problems + +### Repeating a test case +In order to find sporadic test failures the `--repeat` flag can be used. +```sh +ctest --test-dir build -R cpp --repeat-until-fail 10 ``` +There are more options to `ctest` such as `--progress`, allowing to have a less verbose output. +They can be found in the [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) man page. + Note that any development should be performed using a git clone, and the current development branch. The source tarballs do not reflect HEAD, and do not contain the test suite. Follow the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) for creating a pull request. + +## Using a Custom Version of TaskChampion + +To build against a different version of Taskchampion, modify the requirement in `src/taskchampion-cpp/Cargo.toml`. + +To build from a local checkout, replace the version with a [path dependency](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies), giving the path to the directory containing TaskChampion's `Cargo.toml`: + +```toml +taskchampion = { path = "path/to/taskchampion" } +``` diff --git a/doc/devel/contrib/releasing.md b/doc/devel/contrib/releasing.md index 15fd1292a..ffaf1ea67 100644 --- a/doc/devel/contrib/releasing.md +++ b/doc/devel/contrib/releasing.md @@ -11,7 +11,6 @@ To release Taskwarrior, follow this process: - On `develop` after that PR merges, create a release tarball: - `git clone . release-tarball` - `cd release-tarball/` - - edit `Cargo.toml` to contain only `taskchampion` and `taskchampion-lib` in `members` (see https://github.com/GothenburgBitFactory/taskwarrior/issues/3294). - `cmake -S. -Bbuild` - `make -Cbuild package_source` - copy build/task-*.tar.gz elsewhere and delete the `release-tarball` dir @@ -21,4 +20,10 @@ To release Taskwarrior, follow this process: - Find the tag under https://github.com/GothenburgBitFactory/taskwarrior/tags and create a release from it - Give it a clever title if you can think of one; refer to previous releases - Include the tarball from earlier -- Add a new item in `content/news` on https://github.com/GothenburgBitFactory/tw.org +- Update https://github.com/GothenburgBitFactory/tw.org + - Add a new item in `content/news` + - Update `data/projects.json` with the latest version and a fake next version for "devel" + - Update `data/releases.json` with the new version, and copy the tarball into `content/download`. +- Update various things, in a new PR: + - `cargo update` + - `git submodule update --remote --merge` diff --git a/doc/devel/contrib/rust-and-c++.md b/doc/devel/contrib/rust-and-c++.md index e0244424d..d658e850d 100644 --- a/doc/devel/contrib/rust-and-c++.md +++ b/doc/devel/contrib/rust-and-c++.md @@ -1,42 +1,18 @@ # Rust and C++ -Taskwarrior has historically been a C++ project, but as part of an [ongoing effort to replace its storage backend](https://github.com/GothenburgBitFactory/taskwarrior/issues/2770) it now includes a Rust library called TaskChampion. -To develop Taskwarrior, you will need both a [C++ compiler and tools](./development.md), and a [Rust toolchain](https://www.rust-lang.org/tools/install). -However, most tasks will only require you to be familiar with one of the two languages. +Taskwarrior has historically been a C++ project, but as of taskwarrior-3.0.0, the storage backend is now provided by a Rust library called TaskChampion. ## TaskChampion TaskChampion implements storage and access to "replicas" containing a user's tasks. It defines an abstract model for this data, and also provides a simple Rust API for manipulating replicas. It also defines a method of synchronizing replicas and provides an implementation of that method in the form of a sync server. -TaskChampion provides a C interface via the `taskchampion-lib` crate. Other applications, besides Taskwarrior, can use TaskChampion to manage tasks. -Applications written in Rust can use the `taskchampion` crate, while those in other languages may use the `taskchampion-lib` crate. Taskwarrior is just one application using the TaskChampion interface. -You can build TaskChampion locally by simply running `cargo build` in the root of this repository. -The implementation, including more documentation, is in the [`rust`](../../rust) subdirectory. - ## Taskwarrior's use of TaskChampion -Taskwarrior's interface to TaskChampion has a few layers: - -* The skeletal Rust crate in [`src/tc/rust`](../../src/tc/rust) brings the symbols from `taskchampion-lib` under CMake's management. - The corresponding header file is included from [`taskchampion/lib`](../../taskchampion/lib). - All of these symbols are placed in the C++ namespace, `tc::ffi`. -* C++ wrappers for the types from `taskchampion-lib` are defined in [`src/tc`](../../src/tc), ensuring memory safety (with `unique_ptr`) and adding methods corresponding to the Rust API's methods. - The wrapper types are in the C++ namespace, `tc`. - -## WARNING About Dependency Tracking - -CMake cannot detect changes to Rust files in under the `taskchampion/` directory. -Running `make` after these files are changed will not incorporate the changes into the resulting executables. -To force re-compilation of the Rust dependencies: - -``` -rm -rf src/tc/rust/x86_64-unknown-linux-gnu/debug/libtc_rust.a -make -``` - -You may need to adjust the `x86_64-unknown-linux-gnu` part of that command depending on what system you are using for development. +Taskwarrior's interface to TaskChampion is in `src/taskchampion-cpp`. +This links to `taskchampion` as a Rust dependency, and uses [cxx](https://cxx.rs) to build a C++ API for it. +That API is defined, and documented, in `src/taskchampion-cpp/src/lib.rs`, and available in the `tc` namespace in C++ code. diff --git a/doc/devel/rfcs/dom.md b/doc/devel/rfcs/dom.md index ccc00515b..2a9a76586 100644 --- a/doc/devel/rfcs/dom.md +++ b/doc/devel/rfcs/dom.md @@ -227,14 +227,14 @@ An example is to make two reports share the same description: $ task config -- report.ls.description rc.report.list.description -This sets the description for the `ls` report to be a reference to the description of the `list` report. +This sets the description for the `ls` report to be a reference to the description of the `list` report. This reference is not evaluated when the entry is written, but is evaluated every time the value is read, thus providing late-bound behavior. Then if the description of the `list` report changes, so does that of the `ls` report automatically. ## Implementation Details -These notes list a series of anticipated changes to the codebase. +These notes list a series of anticipated changes to the codebase. - The `src/columns/Col*` objects will implement type-specific and attribute-specific DOM support. DOM reference lookup will defer to the column objects first. diff --git a/doc/devel/rfcs/task.md b/doc/devel/rfcs/task.md index a1ab4927f..89eb7a9de 100644 --- a/doc/devel/rfcs/task.md +++ b/doc/devel/rfcs/task.md @@ -25,7 +25,7 @@ In brief: "MUST" (or "REQUIRED") means that the item is an absolute requirement ## General Format -The format is JSON, specifically a JSON object as a single line of text, terminated by a newline (U+000D). +The format is JSON, specifically a JSON object as a single line of text, terminated by a line feed (U+000A). The JSON looks like this: @@ -466,4 +466,3 @@ Given that the configuration is not present in the JSON format of a task, any fi This means that if a task contains a UDA, unless the meaning of it is understood, it MUST be preserved. UDAs may have one of four types: string, numeric, date and duration. - diff --git a/doc/man/.gitignore b/doc/man/.gitignore deleted file mode 100644 index 811e2c712..000000000 --- a/doc/man/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -task-color.5 -task-sync.5 -task.1 -taskrc.5 diff --git a/doc/man/task-color.5.in b/doc/man/task-color.5.in index 493fe713b..fe698f998 100644 --- a/doc/man/task-color.5.in +++ b/doc/man/task-color.5.in @@ -8,14 +8,18 @@ It should be mentioned that Taskwarrior is aware of whether its output is going to a terminal, or to a file or through a pipe. When Taskwarrior output goes to a terminal, color is desirable, but consider the following command: +.nf $ task list > file.txt +.fi Do we really want all those color control codes in the file? Taskwarrior assumes that you do not, and temporarily sets color to 'off' while generating the output. This explains the output from the following command: +.nf $ task show | grep '^color ' color off +.fi it always returns 'off', no matter what the setting, because the output is being sent to a pipe. @@ -23,20 +27,26 @@ sent to a pipe. If you wanted those color codes, you can override this behavior by setting the _forcecolor variable to on, like this: +.nf $ task config _forcecolor on $ task config | grep '^color ' color on +.fi or by temporarily overriding it like this: +.nf $ task rc._forcecolor=on config | grep '^color ' color on +.fi .SH AVAILABLE COLORS Taskwarrior has a 'color' command that will show all the colors it is capable of displaying. Try this: +.nf $ task color +.fi The output cannot be replicated here in a man page, but you should see a set of color samples. How many you see depends on your terminal program's ability to @@ -48,7 +58,9 @@ You should at least see the Basic colors and Effects - if you do, then you have .SH 16-COLOR SUPPORT The basic color support is provided through named colors: +.nf black, red, blue, green, magenta, cyan, yellow, white +.fi Foreground color (for text) is simply specified as one of the above colors, or not specified at all to use the default terminal text color. @@ -56,37 +68,49 @@ not specified at all to use the default terminal text color. Background color is specified by using the word 'on', and one of the above colors. Some examples: +.nf green # green text, default background color green on yellow # green text, yellow background on yellow # default text color, yellow background +.fi These colors can be modified further, by making the foreground bold, or by making the background bright. Some examples: +.nf bold green bold white on bright red on bright cyan +.fi The order of the words is not important, so the following are equivalent: +.nf bold green green bold +.fi But the 'on' is important - colors before the 'on' are foreground, and colors after 'on' are background. There is an additional 'underline' attribute that may be used: +.nf underline bold red on black +.fi And an 'inverse' attribute: +.nf inverse red +.fi Taskwarrior has a command that helps you visualize these color combinations. Try this: +.nf $ task color underline bold red on black +.fi You can use this command to see how the various color combinations work. You will also see some sample colors displayed, like the ones above, in addition to @@ -103,11 +127,13 @@ Using 256 colors follows the same form, but the names are different, and some colors can be referenced in different ways. First there is by color ordinal, which is like this: +.nf color0 color1 color2 ... color255 +.fi This gives you access to all 256 colors, but doesn't help you much. This range is a combination of 8 basic colors (color0 - color7), then 8 brighter variations @@ -119,31 +145,43 @@ be addressed via RGB values from 0 to 5 for each component color. A value of 0 means none of this component color, and a value of 5 means the most intense component color. For example, a bright red is specified as: +.nf rgb500 +.fi And a darker red would be: +.nf rgb300 +.fi Note that the three digits represent the three component values, so in this example the 5, 0 and 0 represent red=5, green=0, blue=0. Combining intense red with no green and no blue yields red. Similarly, blue and green are: +.nf rgb005 rgb050 +.fi Another example - bright yellow - is a mix of bright red and bright green, but no blue component, so bright yellow is addressed as: +.nf rgb550 +.fi A soft pink would be addressed as: +.nf rgb515 +.fi See if you agree, by running: +.nf $ task color black on rgb515 +.fi You may notice that the large color block is represented as 6 squares. All colors in the first square have a red value of 0. All colors in the 6th square @@ -163,7 +201,9 @@ will be disappointed, perhaps even appalled. There is some limited color mapping - for example, if you were to specify this combination: +.nf red on gray3 +.fi you are mixing a 16-color and 256-color specification. Taskwarrior will map red to color1, and proceed. Note that red and color1 are not quite the same tone. @@ -175,7 +215,9 @@ colors, but there is still underline available. Taskwarrior will show examples of all defined colors used in your .taskrc, or theme, if you run this command: +.nf $ task color legend +.fi This gives you an example of each of the colors, so you can see the effect, without necessarily creating a set of tasks that meet each of the rule criteria. @@ -185,20 +227,26 @@ Taskwarrior supports colorization rules. These are configuration values that specify a color, and the conditions under which that color is used. By example, let us add a few tasks: +.nf $ task add project:Home priority:H pay the bills (1) $ task add project:Home clean the rug (2) $ task add project:Garden clean out the garage (3) +.fi We can add a color rule that uses a blue background for all tasks in the Home project: +.nf $ task config color.project.Home 'on blue' +.fi We use quotes around 'on blue' because there are two words, but they represent one value in the .taskrc file. Now suppose we wish to use a bold yellow text color for all cleaning work: +.nf $ task config color.keyword.clean 'bold yellow' +.fi Now what happens to task 2, which belongs to project Home (blue background), and is also a cleaning task (bold yellow foreground)? The colors are combined, and @@ -219,7 +267,9 @@ color blending. The precedence for the color rules is determined by the configuration variable 'rule.precedence.color', which by default contains: +.nf deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda. +.fi These are just the color rules with the 'color.' prefix removed. The rule 'color.deleted' has the highest precedence, and 'color.uda.' the lowest. @@ -228,7 +278,7 @@ The keyword rule shown here as 'keyword.' corresponds to a wildcard pattern, meaning 'color.keyword.*', or in other words all the keyword rules. There is also 'color.project.none', 'color.tag.none' and -'color.uda.priority.none' to specifically represent missing data. +\[aq]color.uda.priority.none' to specifically represent missing data. .SH THEMES Taskwarrior supports themes. What this really means is that with the ability to @@ -238,50 +288,38 @@ be included. To get a good idea of what a color theme looks like, try adding this entry to your .taskrc file: -.RS -include dark-256.theme -.RE +.nf + include dark-256.theme +.fi You can use any of the standard Taskwarrior themes: -.RS -dark-16.theme -.br -dark-256.theme -.br -dark-blue-256.theme -.br -dark-gray-256.theme -.br -dark-green-256.theme -.br -dark-red-256.theme -.br -dark-violets-256.theme -.br -dark-yellow-green.theme -.br -light-16.theme -.br -light-256.theme -.br -solarized-dark-256.theme -.br -solarized-light-256.theme -.br -dark-default-16.theme -.br -dark-gray-blue-256.theme -.br -no-color.theme -.RE +.nf + dark-16.theme + dark-256.theme + dark-blue-256.theme + dark-gray-256.theme + dark-green-256.theme + dark-red-256.theme + dark-violets-256.theme + dark-yellow-green.theme + light-16.theme + light-256.theme + solarized-dark-256.theme + solarized-light-256.theme + dark-default-16.theme + dark-gray-blue-256.theme + no-color.theme +.fi Bear in mind that if you are using a terminal with a dark background, you will see better results using a dark theme. You can also see how the theme will color the various tasks with the command: +.nf $ task color legend +.fi Better yet, create your own, and share it. We will gladly host the theme file on . diff --git a/doc/man/task-sync.5.in b/doc/man/task-sync.5.in index 70c45d216..1560e2d8a 100644 --- a/doc/man/task-sync.5.in +++ b/doc/man/task-sync.5.in @@ -30,12 +30,11 @@ the existing replica, and run `task sync`. .SS When to Synchronize -Taskwarrior can perform a sync operation at every garbage collection (gc) run. -This is the default, and is appropriate for local synchronization. - -For synchronization to a server, a better solution is to run +For synchronization to a server, a common solution is to run +.nf $ task sync +.fi periodically, such as via .BR cron (8) . @@ -52,7 +51,9 @@ For most of these, you will need an encryption secret used to encrypt and decrypt your tasks. This can be any secret string, and must match for all replicas sharing tasks. +.nf $ task config sync.encryption_secret +.fi Tools such as .BR pwgen (1) @@ -64,16 +65,22 @@ To synchronize your tasks to a sync server, you will need the following information from the server administrator: .br - - The server's URL ("origin", such as "https://tw.example.com") + - The server's URL (such as "https://tw.example.com/path") .br - A client ID ("client_id") identifying your tasks Configure Taskwarrior with these details: - $ task config sync.server.origin +.nf + $ task config sync.server.url $ task config sync.server.client_id +.fi -Note that the origin must include the scheme, such as 'http://' or 'https://'. +Note that the URL must include the scheme, such as 'http://' or 'https://'. + + $ task config sync.server.origin + +Is a deprecated synonym for "sync.server.url". .SS Google Cloud Platform @@ -83,14 +90,18 @@ the bucket are adequate. Authenticate to the project with: +.nf $ gcloud config set project $PROJECT_NAME $ gcloud auth application-default login +.fi Then configure Taskwarrior with: +.nf $ task config sync.gcp.bucket +.fi -However you can bring your own service account credentials if your +However you can bring your own service account credentials if your `application-default` is already being used by some other application To begin, navigate to the "IAM and Admin" section in the Navigation Menu, then select "Roles." @@ -101,30 +112,111 @@ Provide an appropriate name and description for the new role. Add permissions to your new role using the filter "Service:storage" (not the "Filter permissions by role" input box). Select the following permissions: - - storage.buckets.create - - storage.buckets.get + - storage.buckets.create + - storage.buckets.get - storage.buckets.update - storage.objects.create + - storage.objects.delete - storage.objects.get - storage.objects.list - storage.objects.update - Create your new role. +Create your new role. - On the left sidebar, navigate to "Service accounts." +On the left sidebar, navigate to "Service accounts." - On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT." - Provide an appropriate name and description for the new service account. - Select the role you just created and complete the service account creation process. - - Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar. - Click on "ADD KEY" to create and download a new key (a JSON key). +On the top menu bar within the "Service accounts" section, click "CREATE SERVICE ACCOUNT." +Provide an appropriate name and description for the new service account. +Select the role you just created and complete the service account creation process. +Now, in the Service Account dashboard, click into the new service account and select "keys" on the top menu bar. +Click on "ADD KEY" to create and download a new key (a JSON key). Then configure Taskwarrior with: +.nf $ task config sync.gcp.bucket - $ task config sync.gcp.credential_path + $ task config sync.gcp.credential_path +.fi + +.SS Amazon Web Services + +To synchronize your tasks to AWS, select a region near you and use the AWS +console to create a new S3 bucket. The default settings for the bucket are +adequate. In particular, ensure that no lifecycle policies are enabled, as they +may automatically delete or transition objects, potentially impacting data +availability. + +You will also need an AWS IAM user with the following policy, where BUCKETNAME +is the name of the bucket. The same user can be configured for multiple +Taskwarrior clients. + +.nf + { + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "TaskChampion", + "Effect": "Allow", + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:ListBucket", + "s3:DeleteObject" + ], + "Resource": [ + "arn:aws:s3:::BUCKETNAME", + "arn:aws:s3:::BUCKETNAME/*" + ] + } + ] + } +.fi + +To create such a user, create a new policy in the IAM console, select the JSON +option in the policy editor, and paste the policy. Click "Next" and give the +policy a name such as "TaskwarriorSync". Next, create a new user, with a name +of your choosing, select "Attach Policies Directly", and then choose the +newly-created policy. + +You will need access keys configured for the new user. Find the user in the +user list, open the "Security Credentials" tab, then click "Create access key" +and follow the steps. + +At this point, you can choose how to provide those credentials to Taskwarrior. +The simplest is to include them in the Taskwarrior configuration: + +.nf + $ task config sync.aws.region + $ task config sync.aws.bucket + $ task config sync.aws.access_key_id + $ task config sync.aws.secret_access_key +.fi + +Alternatively, you can set up an AWS CLI profile, using a profile name of your +choosing such as "taskwarrior-creds": + +.nf + $ aws configure --profile taskwarrior-creds +.fi + +Enter the access key ID and secret access key. The default region and format +are not important. Then configure Taskwarrior with: + +.nf + $ task config sync.aws.region + $ task config sync.aws.bucket + $ task config sync.aws.profile taskwarrior-creds +.fi + +To use AWS's default credential sources, such as environment variables, the +default profile, or an instance profile, set + +.nf + $ task config sync.aws.region + $ task config sync.aws.bucket + $ task config sync.aws.default_credentials true +.fi .SS Local Synchronization @@ -132,7 +224,9 @@ In order to take advantage of synchronization's side effect of saving disk space without setting up a remote server, it is possible to sync tasks locally. To configure local sync: +.nf $ task config sync.local.server_dir /path/to/sync +.fi The default configuration is to sync to a database in the task directory ("data.location"). @@ -153,7 +247,7 @@ To add a new user to the server, invent a new client ID with a tool like `uuidgen` or an online UUID generator. There is no need to configure the server for this new client ID: the sync server will automatically create a new user whenever presented with a new client ID. Supply the ID, along with the -origin, to the user for inclusion in their Taskwarrior config. The user should +URL, to the user for inclusion in their Taskwarrior config. The user should invent their own "encryption_secret". .SH AVOIDING DUPLICATE RECURRING TASKS @@ -161,11 +255,15 @@ invent their own "encryption_secret". If you run multiple clients that sync to the same server, you will need to run this command on your primary client (the one you use most often): +.nf $ task config recurrence on +.fi And on the other clients, run: +.nf $ task config recurrence off +.fi This protects you against the effects of a sync/duplication bug. @@ -184,7 +282,9 @@ modifying the same task on two machines, without an intervening sync. Setup simply involves creating the directory and modifying your data.location configuration variable like this: +.nf $ task config data.location /path/to/shared/directory +.fi Strengths: .br diff --git a/doc/man/task.1.in b/doc/man/task.1.in index 985efe432..c779b2075 100644 --- a/doc/man/task.1.in +++ b/doc/man/task.1.in @@ -24,18 +24,24 @@ descriptors), project groups, etc. The consists of zero or more search criteria that select tasks. For example, to list all pending tasks belonging to the 'Home' project: +.nf task project:Home list +.fi You can specify multiple filter terms, each of which further restricts the result: +.nf task project:Home +weekend garden list +.fi This example applies three filters: the 'Home' project, the 'weekend' tag, and the description or annotations must contain the character sequence 'garden'. In this example, 'garden' is translated internally to: +.nf description.contains:garden +.fi as a convenient shortcut. The 'contains' here is an attribute modifier, which is used to exert more control over the filter than simply absence or presence. @@ -45,24 +51,30 @@ Note that a filter may have zero terms, which means that all tasks apply to the command. This can be dangerous, and this special case is confirmed, and cannot be overridden. For example, this command: +.nf task modify +work This command has no filter, and will modify all tasks. Are you sure? (yes/no) +.fi will add the 'work' tag to all tasks, but only after confirmation. More filter examples: +.nf task task 28 task +weekend task +bills due.by:eom task project:Home due.before:today task ebeeab00-ccf8-464b-8b58-f7f2d606edfb +.fi By default filter elements are combined with an implicit 'and' operator, but 'or' and 'xor' may also be used, provided parentheses are included: +.nf task '( /[Cc]at|[Dd]og/ or /[0-9]+/ )' +.fi The parentheses isolate the logical term from any default command filter or implicit report filter which would be combined with an implicit 'and'. @@ -71,10 +83,12 @@ A filter may target specific tasks using ID or UUID numbers. To specify multiple tasks use one of these forms (space-separated list of ID numbers, UUID numbers or ID ranges): +.nf task 1 2 3 delete task 1-3 info task 1 2-5 19 modify pri:H task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info +.fi Note that it may be necessary to properly escape special characters as well as quotes in order to avoid their special meanings in the shell. See also the @@ -85,11 +99,13 @@ section 'SPECIFYING DESCRIPTIONS' for more information. The consist of zero or more changes to apply to the selected tasks, such as: +.nf task project:Home task +weekend +garden due:tomorrow task Description/annotation text task /from/to/ <- replace first match task /from/to/g <- replace all matches +.fi .SH SUBCOMMANDS @@ -188,6 +204,7 @@ wish to save it, or pipe it to another command or script to convert it to another format. You'll find these example scripts online at : +.nf export-csv.pl export-sql.py export-xml.py @@ -198,6 +215,7 @@ another format. You'll find these example scripts online at export-ical.pl export-xml.pl export-yad.pl +.fi .TP .B task ghistory.annual @@ -243,12 +261,16 @@ Applies the filter then extracts only the task IDs and presents them as a space-separated list. This is useful as input to a task command, to achieve this: +.nf task $(task project:Home ids) modify priority:H +.fi This example first gets the IDs for the project:Home filter, then sets the priority to H for each of those tasks. This can also be achieved directly: +.nf task project:Home modify priority:H +.fi This command is mainly of use to external scripts. @@ -258,7 +280,9 @@ Applies the filter on all tasks (even deleted and completed tasks) then extracts only the task UUIDs and presents them as a space-separated list. This is useful as input to a task command, to achieve this: +.nf task $(task project:Home status:completed uuids) modify status:pending +.fi This example first gets the UUIDs for the project:Home and status:completed filters, then makes each of those tasks pending again. @@ -385,8 +409,15 @@ if import is to be used in automated workflows. See taskrc(5). For importing other file formats, the standard task release comes with a few example scripts, such as: +.nf import-todo.sh.pl import-yaml.pl +.fi + +.TP +.B task import-v2 +Imports tasks from the Taskwarrior v2.x format. This is used when upgrading from +version 2.x to version 3.x. .TP .B task log @@ -401,6 +432,15 @@ Modifies the existing task with provided information. .B task prepend Prepends description text to an existing task. Is affected by the context. +.TP +.B task purge +Permanently removes the specified tasks from the data files. Only +tasks that are already deleted can be purged. This command has a +local-only effect and changes introduced by it are not synced. +Is affected by the context. + +Warning: causes permanent, non-revertible loss of data. + .TP .B task start Marks the specified tasks as started. Is affected by the context. @@ -423,14 +463,20 @@ parses and evaluates the expression given on the command line. Examples: +.nf task calc 1 + 1 2 +.fi +.nf task calc now + 8d 2015-03-26T18:06:57 +.fi +.nf task calc eom 2015-03-31T23:59:59 +.fi .TP .B task config [ [ | '']] @@ -438,16 +484,22 @@ Add, modify and remove settings directly in the Taskwarrior configuration. This command either modifies the 'name' setting with a new value of 'value', or adds a new entry that is equivalent to 'name=value': +.nf task config name value +.fi This command sets a blank value. This has the effect of suppressing any default value: +.nf task config name '' +.fi Finally, this command removes any 'name=...' entry from the .taskrc file: +.nf task config name +.fi .TP .B task context @@ -455,7 +507,9 @@ Sets the currently active context. See the CONTEXT section. Example: +.nf task context work +.fi .TP .B task context delete @@ -464,7 +518,9 @@ set as active, it will be unset. Example: +.nf task context delete work +.fi .TP .B task context define @@ -473,9 +529,11 @@ does not affect the currently set context, just adds a new context definition. Examples: +.nf task context define work project:Work task context define home project:Home or +home task context define superurgent due:today and +urgent +.fi .TP .B task context list @@ -497,11 +555,10 @@ are important. Running this command generates a summary of similar information that should accompany a bug report. It includes compiler, library and software information. It does not include -any personal information, other than the location and size of your task data -files. +any personal information, other than the location of your task data. -This command also performs a diagnostic scan of your data files looking for -common problems, such as duplicate UUIDs. +This command also performs a diagnostic scan of your data looking for common +problems, such as duplicate UUIDs. .TP .B task execute @@ -544,11 +601,15 @@ The sync command synchronizes data with the Taskserver, if configured. Note: If you use multiple sync clients, make sure this setting (which is the default) is on your primary client: +.nf recurrence=on +.fi and on all other clients (this is not the default): +.nf recurrence=off +.fi This is a workaround to avoid a recurrence bug that duplicates recurring tasks. @@ -608,7 +669,9 @@ by third-party applications. Reports a unique set of attribute values. For example, to see all the active projects: +.nf task +PENDING _unique project +.fi .TP .B task _uuids @@ -655,6 +718,7 @@ Shows the UUIDs and descriptions of matching tasks. Accesses and displays the DOM reference(s). Used to extract individual values from tasks, or the system. Supported DOM references are: +.nf rc. tw.syncneeded tw.program @@ -670,6 +734,7 @@ from tasks, or the system. Supported DOM references are: system.os . . +.fi Note that the 'rc.' reference may need to be escaped using '--' to prevent the reference from being interpreted as an override. @@ -680,8 +745,10 @@ missing value, the command exits with 1. Additionally, some components of the attributes of particular types may be extracted by DOM references. +.nf $ task _get 2.due.year 2015 +.fi For a full list of supported attribute-specific DOM references, consult the online documentation at: @@ -691,14 +758,16 @@ the online documentation at: .TP .B ID -Tasks can be specified uniquely by IDs, which are simply the indexes of the -tasks in the data file. The ID of a task may therefore change, but only when -a command is run that displays IDs. When modifying tasks, it is safe to -rely on the last displayed ID. Always run a report to check you have the right -ID for a task. IDs can be given to task as a sequence, for example, -.br -.B +Tasks can be specified uniquely by IDs, which are the indexes of the "working +set" of tasks (mostly pending and recurrent tasks). The ID of a task may +therefore change, but only when a report that displays IDs is run. When +modifying tasks, it is safe to rely on the last displayed ID. Always run a +report to check you have the right ID for a task. IDs can be given to task as a +sequence, for example: + +.nf task 1,4-10,19 delete +.fi .TP .B +tag|-tag @@ -709,15 +778,18 @@ Certain tags (called 'special tags'), can be used to affect the way tasks are treated. For example, if a task has the special tag 'nocolor', then it is exempt from all color rules. The supported special tags are: +.nf +nocolor Disable color rules processing for this task +nonag Completion of this task suppresses all nag messages +nocal This task will not appear on the calendar +next Elevates task so it appears on 'next' report +.fi There are also virtual tags, which represent task metadata in tag form. These tags do not exist, but can be used to filter tasks. The supported virtual tags are: +.nf ACTIVE Matches if the task is started ANNOTATED Matches if the task has annotations BLOCKED Matches if the task is blocked @@ -725,7 +797,7 @@ are: CHILD Matches if the task has a parent (deprecated in 2.6.0) COMPLETED Matches if the task has completed status DELETED Matches if the task has deleted status - DUE Matches if the task is due + DUE Matches if the task is due within the next 7 days (See rc.due) INSTANCE Matches if the task is a recurrent instance LATEST Matches if the task is the newest added task MONTH Matches if the task is due this month @@ -749,6 +821,7 @@ are: WEEK Matches if the task is due this week YEAR Matches if the task is due this year YESTERDAY Matches if the task was due sometime yesterday +.fi .\" If you update the above list, update src/commands/CmdInfo.cpp and src/commands/CmdTags.cpp as well. @@ -760,6 +833,10 @@ add or remove a virtual tag. .B project: Specifies the project to which a task is related to. +.TP +.B status:pending|deleted|completed|waiting|recurring +Specifies the state of the task. + .TP .B priority:H|M|L or priority: Specifies High, Medium, Low and no priority for a task. @@ -806,6 +883,10 @@ by '-', the specified tasks are removed from the dependency list. .B entry: For report purposes, specifies the date that a task was created. +.TP +.B modified: +Specifies the most recent modification date. + .SH ATTRIBUTE MODIFIERS Attribute modifiers improve filters. Supported modifiers are: @@ -846,9 +927,9 @@ calculated attributes: For example: -.RS -task due.before:eom priority.not:L list -.RE +.nf + task due.before:eom priority.not:L list +.fi The .I before @@ -868,15 +949,21 @@ The modifier is the same as 'before', except it also includes the moment in question. For example: +.nf task add test due:eoy +.fi will be found when using the inclusive filter 'by': +.nf task due.by:eoy +.fi but not when the non-inclusive filter 'before' is used: +.nf task due.before:eoy +.fi this applies equally to other named dates such as 'eom', 'eod', etc; the modifier compares using '<=' rather than '<' like 'before' does. @@ -885,8 +972,10 @@ The .I none modifier requires that the attribute does not have a value. For example: +.nf task priority: list task priority.none: list +.fi are equivalent, and list tasks that do not have a priority. @@ -908,8 +997,10 @@ The .I has modifier is used to search for a substring, such as: +.nf task description.has:foo list task foo list +.fi These are equivalent and will return any task that has 'foo' in the description or annotations. @@ -924,13 +1015,17 @@ The .I startswith modifier matches against the left, or beginning of an attribute, such that: +.nf task project.startswith:H list task project:H list +.fi are equivalent and will match any project starting with 'H'. Matching all projects not starting with 'H' is done with: +.nf task project.not:H list +.fi The .I endswith @@ -941,7 +1036,9 @@ The modifier requires that the attribute contain the whole word specified, such that this: - task description.word:bar list +.nf + task description.word:foo list +.fi Will match the description 'foo bar baz' but does not match 'dog food'. @@ -955,15 +1052,19 @@ modifier. You can use the following operators in filter expressions: +.nf and or xor ! Logical operators < <= = == != !== >= > Relational operators ( ) Precedence +.fi For example: +.nf task due.before:eom priority.not:L list task '( due < eom or priority != L )' list task '! ( project:Home or project:Garden )' list +.fi The .I = @@ -987,32 +1088,44 @@ Note that the parentheses are required when using a logical operator other than the 'and' operator. The reason is that some reports contain filters that must be combined with the command line. Consider this example: +.nf task project:Home or project:Garden list +.fi While this looks correct, it is not. The 'list' report contains a filter of: +.nf task show report.list.filter Config Variable Value ----------------- -------------- report.list.filter status:pending +.fi Which means the example is really: +.nf task status:pending project:Home or project:Garden list +.fi The implied 'and' operator makes it: +.nf task status:pending and project:Home or project:Garden list +.fi This is a precedence error - the 'and' and 'or' need to be grouped using parentheses, like this: +.nf task status:pending and ( project:Home or project:Garden ) list +.fi The original example therefore must be entered as: +.nf task '( project:Home or project:Garden )' list +.fi This includes quotes to escape the parentheses, so that the shell doesn't interpret them and hide them from Taskwarrior. @@ -1020,11 +1133,13 @@ interpret them and hide them from Taskwarrior. There is redundancy between operators, attribute modifiers and other syntactic sugar. For example, the following are all equivalent: +.nf task foo list task /foo/ list task description.contains:foo list task description.has:foo list task 'description ~ foo' list +.fi .SH SPECIFYING DATES AND FREQUENCIES @@ -1038,92 +1153,83 @@ configuration variable .RS .TP Exact specification -task ... due:7/14/2008 +.nf + task ... due:7/14/2008 +.fi .TP ISO-8601 -task ... due:2013-03-14T22:30:00Z +.nf + task ... due:2013-03-14T22:30:00Z +.fi .TP Relative wording -task ... due:now -.br -task ... due:today -.br -task ... due:yesterday -.br -task ... due:tomorrow +.nf + task ... due:now + task ... due:today + task ... due:yesterday + task ... due:tomorrow +.fi .TP Day number with ordinal -task ... due:23rd -.br -task ... due:3wks -.br -task ... due:1day -.br -task ... due:9hrs +.nf + task ... due:23rd + task ... due:3wks + task ... due:1day + task ... due:9hrs +.fi .TP Start of next (work) week (Monday), calendar week (Sunday or Monday), month, quarter and year -.br -task ... due:sow -.br -task ... due:soww -.br -task ... due:socw -.br -task ... due:som -.br -task ... due:soq -.br -task ... due:soy +.nf + task ... due:sow + task ... due:soww + task ... due:socw + task ... due:som + task ... due:soq + task ... due:soy +.fi .TP End of current (work) week (Friday), calendar week (Saturday or Sunday), month, quarter and year -.br -task ... due:eow -.br -task ... due:eoww -.br -task ... due:eocw -.br -task ... due:eom -.br -task ... due:eoq -.br -task ... due:eoy +.nf + task ... due:eow + task ... due:eoww + task ... due:eocw + task ... due:eom + task ... due:eoq + task ... due:eoy +.fi .TP At some point or later -.br -task ... wait:later -.br -task ... wait:someday +.nf + task ... wait:later + task ... wait:someday +.fi This sets the wait date to 12/30/9999. .TP Next occurring weekday -task ... due:fri +.nf + task ... due:fri +.fi .TP Predictable holidays -task ... due:goodfriday -.br -task ... due:easter -.br -task ... due:eastermonday -.br -task ... due:ascension -.br -task ... due:pentecost -.br -task ... due:midsommar -.br -task ... due:midsommarafton -.br -task ... due:juhannus +.nf + task ... due:goodfriday + task ... due:easter + task ... due:eastermonday + task ... due:ascension + task ... due:pentecost + task ... due:midsommar + task ... due:midsommarafton + task ... due:juhannus +.fi .RE .SS FREQUENCIES @@ -1174,7 +1280,8 @@ Context is a user-defined query, which is automatically applied to all commands that filter the task list and to commands that create new tasks (add, log). For example, any report command will have its result affected by the current active context. Here is a list of the commands that are affected: -.IP + +.nf add burndown count @@ -1187,38 +1294,50 @@ active context. Here is a list of the commands that are affected: log prepend projects + purge start stats stop summary tags +.fi All other commands are NOT affected by the context. +.nf $ task list ID Age Project Description Urg 1 2d Sport Run 5 miles 1.42 2 1d Home Clean the dishes 1.14 +.fi +.nf $ task context home Context 'home' set. Use 'task context none' to remove. +.fi +.nf $ task list ID Age Project Description Urg 2 1d Home Clean the dishes 1.14 Context 'home' set. Use 'task context none' to remove. +.fi Task list got automatically filtered for project:Home. +.nf $ task add Vaccuum the carpet Created task 3. Context 'home' set. Use 'task context none' to remove. +.fi +.nf $ task list ID Age Project Description Urg 2 1d Home Clean the dishes 1.14 3 5s Home Vaccuum the carpet 1.14 Context 'home' set. Use 'task context none' to remove. +.fi Note that the newly added task "Vaccuum the carpet" has "project:Home" set automatically. @@ -1229,22 +1348,28 @@ new context's name to the 'context' command. To unset any context, use the 'none' subcommand. +.nf $ task context none Context unset. +.fi +.nf $ task list ID Age Project Description Urg 1 2d Sport Run 5 miles 1.42 2 1d Home Clean the dishes 1.14 3 7s Home Vaccuum the carpet 1.14 +.fi Context can be defined using the 'define' subcommand, specifying both the name of the new context, and it's assigned filter. +.nf $ task context define home project:Home Are you sure you want to add 'context.home.read' with a value of 'project:Home'? (yes/no) yes Are you sure you want to add 'context.home.write' with a value of 'project:Home'? (yes/no) yes Context 'home' successfully defined. +.fi Note that you were separately prompted to set the 'read' and 'write' context. This allows you to specify contexts that only work for reporting commands or @@ -1252,13 +1377,16 @@ only for commands that create tasks. To remove the definition, use the 'delete' subcommand. +.nf $ task context delete home Are you sure you want to remove 'context.home.read'? (yes/no) yes Are you sure you want to remove 'context.home.write'? (yes/no) yes Context 'home' deleted. +.fi To check what is the currently active context, use the 'show' subcommand. +.nf $ task context show Context 'home' with @@ -1266,13 +1394,16 @@ To check what is the currently active context, use the 'show' subcommand. * write filter: '+home' is currently applied. +.fi Contexts can store arbitrarily complex filters. +.nf $ task context define family project:Family or +paul or +nancy Are you sure you want to add 'context.family.read' with a value of 'project:Family or +paul or +nancy'? (yes/no) yes Are you sure you want to add 'context.family.write' with a value of 'project:Family or +paul or +nancy'? (yes/no) no Context 'family' successfully defined. +.fi Contexts are permanent, and the currently set context name is stored in the "context" configuration variable. The context definition is stored in the @@ -1285,13 +1416,17 @@ filter as writeable context. The reason for this decision is that the complex filter in the example does not directly translate to a modification. In fact, if such a context is used as a writeable context, the following happens: +.nf $ task add Call Paul Created task 4. Context 'family' set. Use 'task context none' to remove. +.fi +.nf $ task 4 list ID Age Project Tags Description Urg 4 9min Family nancy paul or or Call Paul 0 +.fi There is no clear mapping between the complex filter used and the modifications @@ -1300,16 +1435,20 @@ operators being present in the description. Taskwarrior does not try to guess the user intention here, and instead, the user is expected to set the "context..write" variable to make their intention explicit, for example: +.nf $ task config context.family.write project:Family Are you sure you want to change the value of 'context.family.write' from 'project:Family or +paul or +nancy' to 'project:Family'? (yes/no) yes Config file /home/tbabej/.config/task/taskrc modified. +.fi +.nf $ task context Name Type Definition Active family read project:Family or +paul or +nancy yes write project:Family yes home read +home no write +home no +.fi Note how read and write contexts differ for context "family", while for context "home" they stay the same. @@ -1318,77 +1457,77 @@ In addition, every configuration parameter can be overridden for the current context, by specifying context..rc.. For example, if the default command for the family context should be displaying the family_report: +.nf $ task config context.family.rc.default.command family_report +.fi .SH COMMAND ABBREVIATION All Taskwarrior commands may be abbreviated as long as a unique prefix is used, for example: -.RS -$ task li -.RE +.nf + $ task li +.fi is an unambiguous abbreviation for -.RS -$ task list -.RE +.nf + $ task list +.fi but -.RS -$ task l -.RE +.nf + $ task l +.fi could be list, ls or long. Note that you can restrict the minimum abbreviation size using the configuration setting: -.RS -abbreviation.minimum=3 -.RE +.nf + abbreviation.minimum=3 +.fi .SH SPECIFYING DESCRIPTIONS Some task descriptions need to be escaped because of the shell and the special meaning of some characters to the shell. This can be done either by adding quotes to the description or escaping the special character: -.RS -$ task add "quoted ' quote" -.br -$ task add escaped \\' quote -.RE +.nf + $ task add "quoted ' quote" + $ task add escaped \\' quote +.fi The argument \-\- (a double dash) tells Taskwarrior to treat all other args as description: -.RS -$ task add -- project:Home needs scheduling -.RE +.nf + $ task add -- project:Home needs scheduling +.fi In other situations, the shell sees spaces and breaks up arguments. For example, this command: -.RS -$ task 123 modify /from this/to that/ -.RE +.nf + $ task 123 modify /from this/to that/ +.fi is broken up into several arguments, which is corrected with quotes: -.RS -$ task 123 modify "/from this/to that/" -.RE +.nf + $ task 123 modify "/from this/to that/" +.fi It is sometimes necessary to force the shell to pass quotes to Taskwarrior intact, so you can use: -.RS -$ task add project:\\'Three Word Project\\' description -.RE +.nf + $ task add project:\\'Three Word Project\\' description +.fi -Taskwarrior supports Unicode using only the UTF8 encoding, with no Byte Order -Marks in the data files. +Taskwarrior supports Unicode using only the UTF8 encoding. .SH CONFIGURATION FILE AND OVERRIDE OPTIONS Taskwarrior stores its configuration in a file in the user's home directory: @@ -1439,21 +1578,13 @@ will check if $XDG_CONFIG_HOME/task/taskrc exists and attempt to read it .TP ~/.task -The default directory where task stores its data files. The location -can be configured in the configuration variable 'data.location', or -overridden with the TASKDATA environment variable.. +The default directory where task stores its data. The location can be +configured in the configuration variable 'data.location', or overridden with +the TASKDATA environment variable. .TP -~/.task/pending.data -The file that contains the tasks that are not yet done. - -.TP -~/.task/completed.data -The file that contains the completed ("done") tasks. - -.TP -~/.task/undo.data -The file that contains information needed by the "undo" command. +~/.task/taskchampion.sqlite3 +The database file. .SH "CREDITS & COPYRIGHTS" Copyright (C) 2006 \- 2021 T. Babej, P. Beckingham, F. Hernandez. diff --git a/doc/man/taskrc.5.in b/doc/man/taskrc.5.in index 90fc4790b..9b8e4903a 100644 --- a/doc/man/taskrc.5.in +++ b/doc/man/taskrc.5.in @@ -18,43 +18,43 @@ obtains its configuration data from a file called .I .taskrc \&. This file is normally located in the user's home directory: -.RS -$HOME/.taskrc -.RE +.nf + $HOME/.taskrc +.fi The default location can be overridden using the .I rc: attribute when running task: -.RS -$ task rc:/.taskrc ... -.RE +.nf + $ task rc:/.taskrc ... +.fi or using the TASKRC environment variable: -.RS -$ TASKRC=/tmp/.taskrc task ... -.RE +.nf + $ TASKRC=/tmp/.taskrc task ... +.fi Additionally, if no ~/.taskrc exists, taskwarrior will check if the XDG_CONFIG_HOME environment variable is defined: -.RS -$ XDG_CONFIG_HOME=~/.config task ... -.RE +.nf + $ XDG_CONFIG_HOME=~/.config task ... +.fi Individual options can be overridden by using the .I rc.: attribute when running task: -.RS -$ task rc.: ... -.RE +.nf + $ task rc.: ... +.fi or -.RS -$ task rc.= ... -.RE +.nf + $ task rc.= ... +.fi If .B Taskwarrior @@ -65,9 +65,9 @@ file in the user's home directory. The .taskrc file follows a very simple syntax defining name/value pairs: -.RS - = -.RE +.nf + = +.fi There may be whitespace around , '=' and , and it is ignored. Whitespace within the is left intact. @@ -77,11 +77,11 @@ Values support UTF8 as well as JSON encoding, such as \\uNNNN. Note that Taskwarrior is flexible about the values used to represent Boolean items. You can use "1" to enable, anything else is interpreted as disabled. -The values "on", "yes", "y" and "true" are currently supported but deprecated. +The values "on", "yes", "y" and "true" are also supported. -.RS -include -.RE +.nf + include +.fi There may be whitespace around 'include' and . The file may be an absolute or relative path, and the special character '~' is expanded to mean @@ -95,9 +95,9 @@ respect to the following directories (listed in order of precedence): Note that environment variables are also expanded in paths (and any other taskrc variables). -.RS -# -.RE +.nf + # +.fi A comment consists of the character '#', and extends from the '#' to the end of the line. There is no way to comment a multi-line block. There may be @@ -108,9 +108,9 @@ that makes use of every default. The contents of the .taskrc file therefore represent overrides of the default values. To remove a default value completely there must be an entry like this: -.RS - = -.RE +.nf + = +.fi This entry overrides the default value with a blank value. @@ -118,28 +118,28 @@ This entry overrides the default value with a blank value. You can edit your .taskrc file by hand if you wish, or you can use the 'config' command. To permanently set a value in your .taskrc file, use this command: -.RS -$ task config nag "You have more urgent tasks." -.RE +.nf + $ task config nag "You have more urgent tasks." +.fi To delete an entry, use this command: -.RS -$ task config nag -.RE +.nf + $ task config nag +.fi Taskwarrior will then use the default value. To explicitly set a value to blank, and therefore avoid using the default value, use this command: -.RS -$ task config nag "" -.RE +.nf + $ task config nag "" +.fi Taskwarrior will also display all your settings with this command: -.RS -$ task show -.RE +.nf + $ task show +.fi and in addition, will also perform a check of all the values in the file, warning you of anything it finds amiss. @@ -149,20 +149,19 @@ The .taskrc can include other files containing configuration settings by using t .B include statement: -.RS -include -.RE +.nf + include +.fi By using include files you can divide your main configuration file into several ones containing just the relevant configuration data like colors, etc. There are two excellent uses of includes in your .taskrc, shown here: -.RS -include holidays.en-US.rc -.br -include dark-16.theme -.RE +.nf + include holidays.en-US.rc + include dark-16.theme +.fi This includes two standard files that are distributed with Taskwarrior, which define a set of US holidays, and set up a 16-color theme to use, to color the @@ -173,7 +172,7 @@ These environment variables override defaults, but not command-line arguments. .TP .B TASKDATA=~/.task -This overrides the default path for the Taskwarrior data files. +This overrides the default path for the Taskwarrior data. .TP .B TASKRC=~/.taskrc @@ -197,7 +196,7 @@ Valid variable names and their default values are: .TP .B data.location=$HOME/.task -This is a path to the directory containing all the Taskwarrior files. By +This is a path to the directory containing all the Taskwarrior data. By default, it is set up to be ~/.task, for example: /home/paul/.task Note that you can use the @@ -211,19 +210,20 @@ Note that the TASKDATA environment variable overrides this setting. This is a path to the hook scripts directory. By default it is ~/.task/hooks. .TP -.B locking=1 -Determines whether to use file locking when accessing the pending.data and -completed.data files. Defaults to "1". Solaris users who store the data -files on an NFS mount may need to set locking to "0". Note that there is -danger in setting this value to "0" - another program (or another instance of -task) may write to the task.pending file at the same time. +.B gc=1 +Can be used to temporarily suspend rebuilding, so that task IDs don't change. +Rebuilding requires read/write access to the database, so disabling `gc` may +result in better performance. + +Note that this should be used in the form of a command line override (task +rc.gc=0 ...), and not permanently used in the .taskrc file, as this +significantly affects performance in the long term. .TP -.B gc=1 -Can be used to temporarily suspend garbage collection (gc), so that task IDs -don't change. Note that this should be used in the form of a command line -override (task rc.gc=0 ...), and not permanently used in the .taskrc file, -as this significantly affects performance in the long term. +.B purge.on-sync=0 +If set, old tasks will be purged automatically after each synchronization. +Tasks are identified as "old" when they have status "Deleted" and have not +been modified for 180 days. .TP .B hooks=1 @@ -241,6 +241,13 @@ rc.data.location or TASKDATA override) is missing. Default value is '0'. Determines whether to use ioctl to establish the size of the window you are using, for text wrapping. +.TP +.B limit:25 +Specifies the desired number of tasks a report should show, if a positive +integer is given. The value 'page' may also be used, and will limit the +report output to as many lines of text as will fit on screen. Default value +is '25'. + .TP .B defaultwidth=80 The width of output used when auto-detection support is not available. Defaults @@ -292,12 +299,14 @@ is most readily parsed and used by shell scripts. Alternatively, you can specify a comma-separated list of verbosity tokens that control specific occasions when output is generated. This list may contain: +.nf blank Inserts extra blank lines in output, for clarity header Messages that appear before report output (this includes .taskrc/.task overrides and the "[task next]" message) footnote Messages that appear after report output (mostly status messages and change descriptions) label Column labels on tabular reports new-id Provides feedback on any new task with IDs (and UUIDs for new tasks with ID 0, such as new completed tasks). new-uuid Provides feedback on any new task with UUIDs. Overrides new-id. Useful for automation. + news Reminds to read new release highlights until the user runs "task news". affected Reports 'N tasks affected' and similar edit Used the verbose template for the 'edit' command special Feedback when applying special tags @@ -308,6 +317,7 @@ control specific occasions when output is generated. This list may contain: override Notification when configuration options are overridden recur Notification when a new recurring task instance is created default Notifications about taskwarrior choosing to perform a default action. +.fi The tokens "affected", "new-id", "new-uuid", "project", "override" and "recur" imply "footnote". @@ -319,14 +329,20 @@ and the "nothing" setting is equivalent to none of the tokens being specified. Here are the shortcut equivalents: +.nf verbose=on - verbose=blank,header,footnote,label,new-id,affected,edit,special,project,sync,filter,override,recur + verbose=blank,header,footnote,label,new-id,news,affected,edit,special,project,sync,filter,override,recur +.fi +.nf verbose=0 verbose=blank,label,new-id,edit +.fi +.nf verbose=nothing verbose= +.fi Those additional comments are sent to the standard error for header, footnote and project. The others are sent to standard output. @@ -495,13 +511,6 @@ weekly recurring task is added with a due date of tomorrow, and recurrence.limit is set to 2, then a report will list 2 pending recurring tasks, one for tomorrow, and one for a week from tomorrow. -.TP -.B undo.style=side -When the 'undo' command is run, Taskwarrior presents a before and after -comparison of the data. This can be in either the 'side' style, which compares -values side-by-side in a table, or 'diff' style, which uses a format similar to -the 'diff' command. - .TP .B abbreviation.minimum=2 Minimum length of any abbreviated command/value. This means that "ve", "ver", @@ -577,51 +586,29 @@ are formatted according to dateformat. The default value is the ISO-8601 standard: Y-M-D. The string can contain the characters: -.RS -.RS -m minimal-digit month, for example 1 or 12 -.br -d minimal-digit day, for example 1 or 30 -.br -y two-digit year, for example 09 or 12 -.br -D two-digit day, for example 01 or 30 -.br -M two-digit month, for example 01 or 12 -.br -Y four-digit year, for example 2009 or 2015 -.br -a short name of weekday, for example Mon or Wed -.br -A long name of weekday, for example Monday or Wednesday -.br -b short name of month, for example Jan or Aug -.br -B long name of month, for example January or August -.br -v minimal-digit week, for example 3 or 37 -.br -V two-digit week, for example 03 or 37 -.br -h minimal-digit hour, for example 3 or 21 -.br -n minimal-digit minutes, for example 5 or 42 -.br -s minimal-digit seconds, for example 7 or 47 -.br -H two-digit hour, for example 03 or 21 -.br -N two-digit minutes, for example 05 or 42 -.br -S two-digit seconds, for example 07 or 47 -.br -J three-digit Julian day, for example 023 or 365 -.br -j Julian day, for example 23 or 365 -.br -w Week day, for example 0 for Monday, 5 for Friday -.RE -.RE +.nf + m minimal-digit month, for example 1 or 12 + d minimal-digit day, for example 1 or 30 + y two-digit year, for example 09 or 12 + D two-digit day, for example 01 or 30 + M two-digit month, for example 01 or 12 + Y four-digit year, for example 2009 or 2015 + a short name of weekday, for example Mon or Wed + A long name of weekday, for example Monday or Wednesday + b short name of month, for example Jan or Aug + B long name of month, for example January or August + v minimal-digit week, for example 3 or 37 + V two-digit week, for example 03 or 37 + h minimal-digit hour, for example 3 or 21 + n minimal-digit minutes, for example 5 or 42 + s minimal-digit seconds, for example 7 or 47 + H two-digit hour, for example 03 or 21 + N two-digit minutes, for example 05 or 42 + S two-digit seconds, for example 07 or 47 + J three-digit Julian day, for example 023 or 365 + j Julian day, for example 23 or 365 + w Week day, for example 0 for Monday, 5 for Friday +.fi .RS The characters 'v', 'V', 'a' and 'A' can only be used for formatting printed @@ -633,37 +620,24 @@ The string may also contain other characters to act as spacers, or formatting. Examples for other values of dateformat: .RE -.RS -.RS -.br -d/m/Y would use for input and output 24/7/2009 -.br -yMD would use for input and output 090724 -.br -M-D-Y would use for input and output 07-24-2009 -.RE -.RE +.nf + d/m/Y would use for input and output 24/7/2009 + yMD would use for input and output 090724 + M-D-Y would use for input and output 07-24-2009 +.fi .RS Examples for other values of dateformat.report: .RE -.RS -.RS -.br -a D b Y (V) would emit "Fri 24 Jul 2009 (30)" -.br -A, B D, Y would emit "Friday, July 24, 2009" -.br -wV a Y-M-D would emit "w30 Fri 2009-07-24" -.br -yMD.HN would emit "110124.2342" -.br -m/d/Y H:N would emit "1/24/2011 10:42" -.br -a D b Y H:N:S would emit "Mon 24 Jan 2011 11:19:42" -.RE -.RE +.nf + a D b Y (V) would emit "Fri 24 Jul 2009 (30)" + A, B D, Y would emit "Friday, July 24, 2009" + wV a Y-M-D would emit "w30 Fri 2009-07-24" + yMD.HN would emit "110124.2342" + m/d/Y H:N would emit "1/24/2011 10:42" + a D b Y H:N:S would emit "Mon 24 Jan 2011 11:19:42" +.fi .RS Undefined fields are put to their minimal valid values (1 for month and day and @@ -672,14 +646,10 @@ field that is set. Otherwise, they are set to the corresponding values of "now". For example: .RE -.RS -.RS -.br -8/1/2013 with m/d/Y implies August 1, 2013 at midnight (inferred) -.br -8/1 20:40 with m/d H:N implies August 1, 2013 (inferred) at 20:40 -.RE -.RE +.nf + 8/1/2013 with m/d/Y implies August 1, 2013 at midnight (inferred) + 8/1 20:40 with m/d H:N implies August 1, 2013 (inferred) at 20:40 +.fi .TP .B date.iso=1 @@ -773,28 +743,19 @@ Holidays are entered either directly in the .taskrc file or via an include file that is specified in .taskrc. For single-day holidays the name and the date is required to be given: -.RS -.RS -.br -holiday.towel.name=Day of the towel -.br -holiday.towel.date=20100525 -.RE -.RE +.nf + holiday.towel.name=Day of the towel + holiday.towel.date=20100525 +.fi For holidays that span a range of days (i.e. vacation), you can use a start date and an end date: -.RS -.RS -.br -holiday.sysadmin.name=System Administrator Appreciation Week -.br -holiday.sysadmin.start=20100730 -.br -holiday.sysadmin.end=20100805 -.RE -.RE +.nf + holiday.sysadmin.name=System Administrator Appreciation Week + holiday.sysadmin.start=20100730 + holiday.sysadmin.end=20100805 +.fi .RS Dates are to be entered according to the setting in the dateformat.holiday @@ -807,24 +768,17 @@ Easter (easter), Easter Monday (eastermonday), Ascension (ascension), Pentecost (pentecost). The date for these holidays is the given keyword: .RE -.RS -.RS -.br -holiday.eastersunday.name=Easter -.br -holiday.eastersunday.date=easter -.RE -.RE +.nf + holiday.eastersunday.name=Easter + holiday.eastersunday.date=easter +.fi Note that the Taskwarrior distribution contains example holiday files that can be included like this: -.RS -.RS -.br -include holidays.en-US.rc -.RE -.RE +.nf + include holidays.en-US.rc +.fi .SS DEPENDENCIES @@ -912,10 +866,9 @@ Task is deleted. .RS To disable a coloration rule for which there is a default, set the value to nothing, for example: -.RS -.B color.tagged= -.RE -.RE +.nf + color.tagged= +.fi .RS By default, colors produced by rules blend. This has the advantage of @@ -1092,6 +1045,9 @@ yellow bars. .RS Colors used by the undo command, to indicate the values both before and after a change that is to be reverted. + +Currently not supported. + .RE .TP @@ -1250,30 +1206,23 @@ default.command=next Provides a default command that is run every time Taskwarrior is invoked with no arguments. For example, if set to: -.RS -.RS -default.command=project:foo list -.RE -.RE +.nf + default.command=project:foo list +.fi .RS then Taskwarrior will run the "project:foo list" command if no command is specified. This means that by merely typing .RE -.RS -.RS -$ task -.br -[task project:foo list] -.br -\& -.br -ID Project Pri Description - 1 foo H Design foo - 2 foo Build foo -.RE -.RE +.nf + $ task + [task project:foo list] + + ID Project Pri Description + 1 foo H Design foo + 2 foo Build foo +.fi .SS REPORTS @@ -1312,12 +1261,16 @@ specified by using the column ids post-fixed by a "+" for ascending sort order or a "-" for descending sort order. The sort IDs are separated by commas. For example: +.nf report.list.sort=due+,priority-,start.active-,project+ +.fi Additionally, after the "+" or "-", there can be a solidus "/" which indicates that there are breaks after the column values change. For example: +.nf report.minimal.sort=project+/,description+ +.fi This sort order now specifies that there is a listing break between each project. A listing break is simply a blank line, which provides a visual @@ -1431,7 +1384,7 @@ if you define a UDA named 'estimate', Taskwarrior will not know that this value is weeks, hours, minutes, money, or some other resource count. .TP -.B uda..type=string|numeric|date|duration +.B uda..type=string|numeric|uuid|date|duration .RS Defines a UDA called '', of the specified type. .RE diff --git a/doc/rc/bubblegum-256.theme b/doc/rc/bubblegum-256.theme new file mode 100644 index 000000000..c79db532a --- /dev/null +++ b/doc/rc/bubblegum-256.theme @@ -0,0 +1,99 @@ +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +# Theme author: Adrian Galilea @adriangalilea + +rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda. + +# General decoration +color.label= +color.label.sort= +color.alternate=on gray2 +color.header=rgb013 +color.footnote=rgb013 +color.warning=rgb520 +color.error=red +color.debug=blue + +# Task state +color.completed= +color.deleted= +color.active=rgb553 +color.recurring=bright rgb535 +color.scheduled= +color.until= +color.blocked=gray10 +color.blocking=on rgb002 + +# Project +color.project.none= + +# Priority +color.uda.priority.H=rgb435 +color.uda.priority.L=gray15 + +# Tags +color.tag.next=rgb253 +color.tag.none= +color.tagged= + +# Due +color.due= +color.due.today=rgb125 +color.overdue=bold inverse + +# Report: burndown +color.burndown.pending=on rgb103 +color.burndown.started=on rgb214 +color.burndown.done=on gray4 + +# Report: history +color.history.add=color0 on rgb105 +color.history.done=color0 on rgb205 +color.history.delete=color0 on rgb305 + +# Report: summary +color.summary.bar=white on rgb104 +color.summary.background=white on rgb001 + +# Command: calendar +color.calendar.due=color0 on rgb325 +color.calendar.due.today=color0 on rgb404 +color.calendar.holiday=color15 on rgb102 +color.calendar.overdue=color0 on color5 +color.calendar.scheduled= +color.calendar.today=color15 on rgb103 +color.calendar.weekend=gray12 on gray3 +color.calendar.weeknumber=rgb104 + +# Command: sync +color.sync.added=gray4 +color.sync.changed=rgb214 +color.sync.rejected=rgb103 + +# Command: undo +color.undo.before=rgb103 +color.undo.after=rgb305 diff --git a/doc/rc/dark-16.theme b/doc/rc/dark-16.theme index 6ac6505d7..41cb07234 100644 --- a/doc/rc/dark-16.theme +++ b/doc/rc/dark-16.theme @@ -86,6 +86,7 @@ color.calendar.due=white on red color.calendar.due.today=bold white on red color.calendar.holiday=black on bright yellow color.calendar.overdue=black on bright red +color.calendar.scheduled= color.calendar.today=bold white on bright blue color.calendar.weekend=white on bright black color.calendar.weeknumber=bold blue @@ -98,4 +99,3 @@ color.sync.rejected=red # Command: undo color.undo.after=green color.undo.before=red - diff --git a/doc/rc/dark-256.theme b/doc/rc/dark-256.theme index 4398fa96a..c6b385371 100644 --- a/doc/rc/dark-256.theme +++ b/doc/rc/dark-256.theme @@ -82,6 +82,7 @@ color.summary.bar=black on rgb141 color.calendar.due.today=color15 on color1 color.calendar.due=color0 on color1 color.calendar.holiday=color0 on color11 +color.calendar.scheduled= color.calendar.overdue=color0 on color9 color.calendar.today=color15 on rgb013 color.calendar.weekend=on color235 @@ -95,4 +96,3 @@ color.sync.rejected=color9 # Command: undo color.undo.after=color2 color.undo.before=color1 - diff --git a/doc/rc/dark-blue-256.theme b/doc/rc/dark-blue-256.theme index 91b1c6ca4..3ab5022a9 100644 --- a/doc/rc/dark-blue-256.theme +++ b/doc/rc/dark-blue-256.theme @@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color252 color.calendar.due=color0 on color249 color.calendar.holiday=color255 on rgb013 color.calendar.overdue=color0 on color255 +color.calendar.scheduled= color.calendar.today=color0 on rgb115 color.calendar.weekend=on color235 color.calendar.weeknumber=rgb015 @@ -95,4 +96,3 @@ color.sync.rejected=rgb004 # Command: undo color.undo.after=rgb035 color.undo.before=rgb013 - diff --git a/doc/rc/dark-gray-256.theme b/doc/rc/dark-gray-256.theme index f169abaa4..3b10a7766 100644 --- a/doc/rc/dark-gray-256.theme +++ b/doc/rc/dark-gray-256.theme @@ -83,6 +83,7 @@ color.calendar.due=on gray8 color.calendar.due.today=black on gray15 color.calendar.holiday=black on gray20 color.calendar.overdue=gray2 on gray10 +color.calendar.scheduled= color.calendar.today=bold white color.calendar.weekend=on gray2 color.calendar.weeknumber=gray6 @@ -95,4 +96,3 @@ color.sync.rejected=gray5 on gray23 # Command: undo color.undo.before=white on black color.undo.after=black on white - diff --git a/doc/rc/dark-gray-blue-256.theme b/doc/rc/dark-gray-blue-256.theme index 97bb242b4..4d495fb2a 100644 --- a/doc/rc/dark-gray-blue-256.theme +++ b/doc/rc/dark-gray-blue-256.theme @@ -83,6 +83,7 @@ color.calendar.due=color0 on gray10 color.calendar.due.today=color0 on gray15 color.calendar.holiday=color15 on rgb005 color.calendar.overdue=color0 on gray20 +color.calendar.scheduled= color.calendar.today=underline black on color15 color.calendar.weekend=on gray4 color.calendar.weeknumber=gray10 @@ -95,4 +96,3 @@ color.sync.rejected=gray23 # Command: undo color.undo.before=rgb013 color.undo.after=rgb035 - diff --git a/doc/rc/dark-green-256.theme b/doc/rc/dark-green-256.theme index b95aa51c3..01d0c0111 100644 --- a/doc/rc/dark-green-256.theme +++ b/doc/rc/dark-green-256.theme @@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color225 color.calendar.due=color0 on color249 color.calendar.holiday=rgb151 on rgb020 color.calendar.overdue=color0 on color255 +color.calendar.scheduled= color.calendar.today=color0 on rgb151 color.calendar.weekend=on color235 color.calendar.weeknumber=rgb010 diff --git a/doc/rc/dark-red-256.theme b/doc/rc/dark-red-256.theme index 414e1077f..a06ab992d 100644 --- a/doc/rc/dark-red-256.theme +++ b/doc/rc/dark-red-256.theme @@ -83,6 +83,7 @@ color.calendar.due.today=color0 on color252 color.calendar.due=color0 on color249 color.calendar.holiday=rgb522 on rgb300 color.calendar.overdue=color0 on color255 +color.calendar.scheduled= color.calendar.today=color0 on rgb511 color.calendar.weekend=on color235 color.calendar.weeknumber=rgb100 @@ -95,4 +96,3 @@ color.sync.rejected=rgb200 # Command: undo color.undo.after=rgb511 color.undo.before=rgb200 - diff --git a/doc/rc/dark-violets-256.theme b/doc/rc/dark-violets-256.theme index 0cd56e6df..c92aceb38 100644 --- a/doc/rc/dark-violets-256.theme +++ b/doc/rc/dark-violets-256.theme @@ -83,6 +83,7 @@ color.calendar.due=color0 on rgb325 color.calendar.due.today=color0 on rgb404 color.calendar.holiday=color15 on rgb102 color.calendar.overdue=color0 on color5 +color.calendar.scheduled= color.calendar.today=color15 on rgb103 color.calendar.weekend=gray12 on gray3 color.calendar.weeknumber=rgb104 @@ -95,4 +96,3 @@ color.sync.rejected=rgb103 # Command: undo color.undo.before=rgb103 color.undo.after=rgb305 - diff --git a/doc/rc/dark-yellow-green.theme b/doc/rc/dark-yellow-green.theme index da7429a46..15144090c 100644 --- a/doc/rc/dark-yellow-green.theme +++ b/doc/rc/dark-yellow-green.theme @@ -83,6 +83,7 @@ color.calendar.due=color0 on rgb440 color.calendar.due.today=color0 on rgb430 color.calendar.holiday=rgb151 on rgb020 color.calendar.overdue=color0 on rgb420 +color.calendar.scheduled= color.calendar.today=color15 on rgb110 color.calendar.weekend=on color235 color.calendar.weeknumber=rgb110 @@ -95,4 +96,3 @@ color.sync.rejected=rgb110 # Command: undo color.undo.before=rgb021 color.undo.after=rgb042 - diff --git a/doc/rc/light-16.theme b/doc/rc/light-16.theme index 885c7367d..c5a0d68a2 100644 --- a/doc/rc/light-16.theme +++ b/doc/rc/light-16.theme @@ -83,6 +83,7 @@ color.calendar.due=on bright green color.calendar.due.today=blue on bright yellow color.calendar.holiday=on yellow color.calendar.overdue=on bright red +color.calendar.scheduled= color.calendar.today=blue color.calendar.weekend=on white color.calendar.weeknumber=blue @@ -95,4 +96,3 @@ color.sync.rejected=red # Command: undo color.undo.before=yellow color.undo.after=green - diff --git a/doc/rc/light-256.theme b/doc/rc/light-256.theme index e8eb896f3..0bba1d170 100644 --- a/doc/rc/light-256.theme +++ b/doc/rc/light-256.theme @@ -65,7 +65,7 @@ color.due.today=on rgb353 color.overdue=on rgb544 # Report: burndown -color.burndown.pending=on rgb411 +color.burndown.pending=on rgb411 color.burndown.started=on rgb550 color.burndown.done=on rgb151 @@ -83,6 +83,7 @@ color.calendar.due=on rgb343 color.calendar.due.today=on rgb353 color.calendar.holiday=color0 on rgb530 color.calendar.overdue=on rgb533 +color.calendar.scheduled= color.calendar.today=rgb005 color.calendar.weekend=on gray21 color.calendar.weeknumber=gray16 @@ -95,4 +96,3 @@ color.sync.rejected=red # Command: undo color.undo.before=yellow color.undo.after=green - diff --git a/doc/rc/no-color.theme b/doc/rc/no-color.theme index af085c04d..1f33b83af 100644 --- a/doc/rc/no-color.theme +++ b/doc/rc/no-color.theme @@ -86,6 +86,7 @@ color.calendar.due= color.calendar.due.today= color.calendar.holiday= color.calendar.overdue= +color.calendar.scheduled= color.calendar.today= color.calendar.weekend= color.calendar.weeknumber= @@ -98,4 +99,3 @@ color.sync.rejected= # Command: undo color.undo.after= color.undo.before= - diff --git a/doc/rc/refresh b/doc/rc/refresh index 361dc85fa..78ce19f45 100755 --- a/doc/rc/refresh +++ b/doc/rc/refresh @@ -6,4 +6,3 @@ do echo $locale ../../scripts/add-ons/update-holidays.pl --locale $locale --file holidays.${locale}.rc done - diff --git a/doc/rc/solarized-dark-256.theme b/doc/rc/solarized-dark-256.theme index b8551d971..74809b80a 100644 --- a/doc/rc/solarized-dark-256.theme +++ b/doc/rc/solarized-dark-256.theme @@ -100,6 +100,7 @@ color.calendar.due=color0 on color9 color.calendar.due.today=color0 on color1 color.calendar.holiday=color0 on color3 color.calendar.overdue=color0 on color5 +color.calendar.scheduled= color.calendar.today=color0 on color4 color.calendar.weekend=on color0 color.calendar.weeknumber=color4 @@ -112,4 +113,3 @@ color.sync.rejected=color13 # Command: undo color.undo.after=color2 color.undo.before=color1 - diff --git a/doc/rc/solarized-light-256.theme b/doc/rc/solarized-light-256.theme index 55c524e01..40c84c658 100644 --- a/doc/rc/solarized-light-256.theme +++ b/doc/rc/solarized-light-256.theme @@ -100,6 +100,7 @@ color.calendar.due=color7 on color9 color.calendar.due.today=color7 on color1 color.calendar.holiday=color7 on color3 color.calendar.overdue=color7 on color5 +color.calendar.scheduled= color.calendar.today=color7 on color4 color.calendar.weekend=on color7 color.calendar.weeknumber=color14 @@ -112,4 +113,3 @@ color.sync.rejected=color13 # Command: undo color.undo.after=color2 color.undo.before=color1 - diff --git a/docker-compose.yml b/docker-compose.yml index 836e638fe..6d3b8bb92 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,17 +1,17 @@ version: '3' services: - test-fedora38: + test-fedora40: build: context: . - dockerfile: test/docker/fedora38 + dockerfile: test/docker/fedora40 network_mode: "host" security_opt: - label=type:container_runtime_t tty: true - test-fedora39: + test-fedora41: build: context: . - dockerfile: test/docker/fedora39 + dockerfile: test/docker/fedora41 network_mode: "host" security_opt: - label=type:container_runtime_t diff --git a/misc/themes/README b/misc/themes/README index 37a373e58..6417a68c3 100644 --- a/misc/themes/README +++ b/misc/themes/README @@ -25,4 +25,3 @@ Using a solarized light terminal, run the following: Note that for the solarized themes, the terminal color palette needs to be set to specific colors. - diff --git a/misc/themes/setup b/misc/themes/setup index 869ba67dc..e2882e8b1 100755 --- a/misc/themes/setup +++ b/misc/themes/setup @@ -30,4 +30,3 @@ task rc:x add Deleted_1 task rc:x 14 mod depends:13 task rc:x 15 delete - diff --git a/performance/.gitignore b/performance/.gitignore deleted file mode 100644 index e82c8156a..000000000 --- a/performance/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.data -*.rc -export.json diff --git a/performance/CMakeLists.txt b/performance/CMakeLists.txt index adc77d414..a8fff9921 100644 --- a/performance/CMakeLists.txt +++ b/performance/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required (VERSION 3.22) -add_custom_target (performance ./run_perf - DEPENDS task_executable - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/performance) +configure_file(compare_runs.py compare_runs.py COPYONLY) +configure_file(load load) +configure_file(run_perf run_perf) +add_custom_target (performance ${CMAKE_BINARY_DIR}/performance/run_perf + DEPENDS task_executable + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/performance) diff --git a/performance/compare_runs.py b/performance/compare_runs.py index da2339740..c4b86ea7c 100755 --- a/performance/compare_runs.py +++ b/performance/compare_runs.py @@ -29,10 +29,13 @@ def parse_perf(input): tests[command] = [] # Parse concatenated run_perf output - for i in re.findall("^ - task %s\.\.\.\n" - "Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$" - % command, input, re.MULTILINE): - info = i[0:3] + ({k:v for k, v in (i.split(":") for i in i[-1].split())},) + for i in re.findall( + "^ - task %s\\.\\.\\.\n" + "Perf task ([^ ]+) ([^ ]+) ([^ ]+) (.+)$" % command, + input, + re.MULTILINE, + ): + info = i[0:3] + ({k: v for k, v in (i.split(":") for i in i[-1].split())},) pt = TaskPerf(*info) tests[command].append(pt) return tests @@ -61,8 +64,14 @@ with open(sys.argv[2], "r") as fh: tests_cur = parse_perf(fh.read()) best_cur = get_best(tests_cur) -print("Previous: %s (%s)" % (tests_prev[COMMANDS[0]][0].version, tests_prev[COMMANDS[0]][0].commit)) -print("Current: %s (%s)" % (tests_cur[COMMANDS[0]][0].version, tests_cur[COMMANDS[0]][0].commit)) +print( + "Previous: %s (%s)" + % (tests_prev[COMMANDS[0]][0].version, tests_prev[COMMANDS[0]][0].commit) +) +print( + "Current: %s (%s)" + % (tests_cur[COMMANDS[0]][0].version, tests_cur[COMMANDS[0]][0].commit) +) for test in COMMANDS: print("# %s:" % test) @@ -76,7 +85,9 @@ for test in COMMANDS: else: percentage = "0%" - pad = max(map(len, (k, best_prev[test][k], best_cur[test][k], diff, percentage))) + pad = max( + map(len, (k, best_prev[test][k], best_cur[test][k], diff, percentage)) + ) out[0] += " %s" % k.rjust(pad) out[1] += " %s" % best_prev[test][k].rjust(pad) out[2] += " %s" % best_cur[test][k].rjust(pad) diff --git a/performance/load b/performance/load index 7f6dded6d..56df91f35 100755 --- a/performance/load +++ b/performance/load @@ -14,7 +14,7 @@ if (open my $fh, '>', 'perf.rc') close $fh; } -my $filename = 'sample-text.txt'; +my $filename = '${CMAKE_SOURCE_DIR}/performance/sample-text.txt'; open(my $fh, '<:encoding(UTF-8)', $filename) or die "Could not open file '$filename' $!"; @@ -31,18 +31,18 @@ while (my $line = <$fh>) if ($. % 20 == 19) { my $anno_id = $id - 1; - qx{../build/src/task rc:perf.rc rc.gc=off $anno_id annotate $line}; + qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off $anno_id annotate $line}; print "[$.] task rc:perf.rc rc.gc=off $anno_id annotate $line\n" if $?; } elsif ($. % 4 == 1) { - qx{../build/src/task rc:perf.rc rc.gc=off add $line}; + qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off add $line}; print "[$.] task rc:perf.rc rc.gc=off add $line\n" if $?; ++$id; } else { - qx{../build/src/task rc:perf.rc rc.gc=off log $line}; + qx{${CMAKE_BINARY_DIR}/src/task rc:perf.rc rc.gc=off log $line}; print "[$.] task rc:perf.rc rc.gc=off log $line\n" if $?; } } diff --git a/performance/run_perf b/performance/run_perf index 869640b63..829fe1723 100755 --- a/performance/run_perf +++ b/performance/run_perf @@ -1,22 +1,22 @@ #! /bin/bash echo 'Performance: setup' -rm -f ./pending.data ./completed.data ./undo.data ./backlog.data perf.rc -if [[ -e data/pending.data && -e data/completed.data ]] +rm -f ./taskchampion.sqlite3 +if [[ -e ./data/taskchampion.sqlite3 ]] then - echo ' - Using existing data' + echo ' - Using existing data.' cp data/* . else - echo ' - This step will take several minutes' + echo ' - Loading data. This step will take several minutes.' ./load mkdir -p data - cp *.data perf.rc data + cp taskchampion.sqlite3 perf.rc data fi # Allow override. if [[ -z $TASK ]] then - TASK=../build/src/task + TASK=${CMAKE_BINARY_DIR}/src/task fi # Run benchmarks. @@ -45,9 +45,8 @@ $TASK rc.debug:1 rc:perf.rc export >/dev/null 2>&1 $TASK rc.debug:1 rc:perf.rc export 2>&1 >export.json | grep "Perf task" echo ' - task import...' -rm -f ./pending.data ./completed.data ./undo.data ./backlog.data -$TASK rc.debug:1 rc:perf.rc import export.json 2>&1 | grep "Perf task" +rm -f ./taskchampion.sqlite3 +$TASK rc.debug:1 rc:perf.rc import ${CMAKE_SOURCE_DIR}/performance/export.json 2>&1 | grep "Perf task" echo 'End' exit 0 - diff --git a/performance/sample-text.txt b/performance/sample-text.txt index 5b6e09ba9..acc17fb25 100644 --- a/performance/sample-text.txt +++ b/performance/sample-text.txt @@ -3963,7 +3963,7 @@ GLOUCESTER Give me the letter, sir. EDMUND I shall offend, either to detain or give it. The contents, as in part I understand them, are to blame. GLOUCESTER Lets see, lets see. EDMUND I hope, for my brothers justification, he wrote this but as an essay or taste of my virtue. -GLOUCESTER This policy and reverence of age makes the world bitter to the best of our times, keeps our fortunes from us till our oldness cannot relish them. I begin to find an idle and fond bondage in the oppression of aged tyranny, who sways, not as it hath power, but as it is suffered. Come to me, that of this I may speak more. If our father would sleep till I waked him, you should enjoy half his revenue for ever, and live the beloved of your brother, +GLOUCESTER This policy and reverence of age makes the world bitter to the best of our times, keeps our fortunes from us till our oldness cannot relish them. I begin to find an idle and fond bondage in the oppression of aged tyranny, who sways, not as it hath power, but as it is suffered. Come to me, that of this I may speak more. If our father would sleep till I waked him, you should enjoy half his revenue for ever, and live the beloved of your brother, EDMUND It was not brought me, my lord, theres the cunning of it, I found it thrown in at the casement of my closet. GLOUCESTER You know the character to be your brothers? EDMUND If the matter were good, my lord, I durst swear it were his, but, in respect of that, I would fain think it were not. diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt index c0f40fe3e..3d06cedaf 100644 --- a/scripts/CMakeLists.txt +++ b/scripts/CMakeLists.txt @@ -8,4 +8,3 @@ install (DIRECTORY add-ons FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - diff --git a/scripts/add-ons/update-holidays.pl b/scripts/add-ons/update-holidays.pl index 7849cbe69..e3360040e 100755 --- a/scripts/add-ons/update-holidays.pl +++ b/scripts/add-ons/update-holidays.pl @@ -221,4 +221,3 @@ if (open my $fh, '>:utf8', $file) exit 0; ################################################################################ - diff --git a/scripts/hooks/README b/scripts/hooks/README index ecfea194e..ecb36e579 100644 --- a/scripts/hooks/README +++ b/scripts/hooks/README @@ -24,4 +24,3 @@ Expected Permissions Interface Each hook script has a unique interface. This is documented in the example scripts here. - diff --git a/scripts/hooks/on-exit.shadow-file b/scripts/hooks/on-exit.shadow-file index 9942d2633..bf8d94d5d 100755 --- a/scripts/hooks/on-exit.shadow-file +++ b/scripts/hooks/on-exit.shadow-file @@ -22,9 +22,8 @@ SHADOW_FILE=$(task _get rc.shadow.file) # rc.detection=off Disables terminal size detection # rc.gc=off Disables GC, thus not changing IDs unexpectedly # rc.color=off Disable color in the shadow file -# rc.locking=off Disable file locking, to prevent race condition # rc.hooks=off Disable hooks, to prevent race condition -task $SHADOW_COMMAND rc.detection=off rc.gc=off rc.color=off rc.locking=off rc.hooks=off > $SHADOW_FILE 2>/dev/null +task $SHADOW_COMMAND rc.detection=off rc.gc=off rc.color=off rc.hooks=off > $SHADOW_FILE 2>/dev/null if [[ $? != 0 ]] then echo Could not create $SHADOW_FILE @@ -33,4 +32,3 @@ fi echo Shadow file $SHADOW_FILE updated. exit 0 - diff --git a/scripts/hooks/on-launch b/scripts/hooks/on-launch index 6f8739374..3a1bcc9fb 100755 --- a/scripts/hooks/on-launch +++ b/scripts/hooks/on-launch @@ -14,4 +14,3 @@ echo 'on-launch' # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 0 - diff --git a/scripts/reproduce-dockerfile b/scripts/reproduce-dockerfile deleted file mode 100644 index 167ea2f3d..000000000 --- a/scripts/reproduce-dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Dockerfile for containers to perform PR review in -# Use with make as follows: make RELEASE=v2.5.1 reproduce - -FROM centos:8 - -RUN dnf update -y -RUN yum install epel-release -y -RUN dnf install python38 vim git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man gdb -y - -RUN useradd warrior -RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior - -USER warrior -WORKDIR /home/warrior/ - -# Setup taskwarrior -# The purpose is to speed up subsequent re-installs due to Docker layer caching -RUN git clone https://github.com/GothenburgBitFactory/taskwarrior.git -WORKDIR /home/warrior/taskwarrior/ -RUN git submodule init - -# Install the given release -ARG RELEASE -RUN git checkout $RELEASE -RUN git submodule update --init -RUN cmake -DCMAKE_BUILD_TYPE=debug . -RUN make -j8 -RUN sudo make install - -# Set the PS1 variable -ENV PS1="[\u@\H \W]\$ " - -WORKDIR /home/warrior -RUN task rc.confirmation=0 _ids || : # Generate default taskrc diff --git a/scripts/review-dockerfile b/scripts/review-dockerfile deleted file mode 100644 index 862b389a5..000000000 --- a/scripts/review-dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Dockerfile for containers to perform PR review in -# Use with make as follows: make PR=1234 review - -FROM centos:8 - -# Workaround to the location of the repos -RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* -RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* - -RUN dnf update -y -RUN yum install epel-release -y -RUN dnf install python38 git gcc gcc-c++ cmake make libuuid-devel libfaketime sudo man -y - -RUN useradd warrior -RUN echo warrior ALL=NOPASSWD:ALL > /etc/sudoers.d/warrior - -USER warrior -WORKDIR /home/warrior/ - -# Setup Rust -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rustup.sh && \ - sh rustup.sh -y --profile minimal --default-toolchain stable --component rust-docs - -# Setup taskwarrior -# The purpose is to speed up subsequent re-installs due to Docker layer caching -RUN git clone https://github.com/GothenburgBitFactory/taskwarrior.git -WORKDIR /home/warrior/taskwarrior/ -RUN git submodule init -RUN git submodule update -RUN cmake -DCMAKE_BUILD_TYPE=debug . -RUN make -j8 -RUN sudo make install - -# Use specified PR's branch, if provided -ARG PR -RUN if [[ ! -z $PR ]]; then \ - git fetch origin refs/pull/${PR}/head:pr-${PR}; \ - git checkout pr-${PR}; fi - -# Use specified libshared PR's branch, if provided -ARG LIBPR -WORKDIR /home/warrior/taskwarrior/src/libshared/ -RUN if [[ ! -z $LIBPR ]]; then \ - git fetch origin refs/pull/${LIBPR}/head:libpr-${LIBPR}; \ - git checkout libpr-${LIBPR}; fi - -# Install taskwarrior -WORKDIR /home/warrior/taskwarrior/ -RUN cmake -DCMAKE_BUILD_TYPE=debug . -RUN make -j8 -RUN sudo make install - -WORKDIR /home/warrior -RUN task rc.confirmation=0 _ids || : # Generate default taskrc diff --git a/scripts/vim/ftdetect/task.vim b/scripts/vim/ftdetect/task.vim index ab3acd9c6..952c825d8 100644 --- a/scripts/vim/ftdetect/task.vim +++ b/scripts/vim/ftdetect/task.vim @@ -1,4 +1,4 @@ -" Vim support file to detect Taskwarrior data and configuration files and +" Vim support file to detect Taskwarrior data and configuration files and " single task edits " " Maintainer: John Florian diff --git a/scripts/vim/syntax/taskrc.vim b/scripts/vim/syntax/taskrc.vim index 62f9d993a..87cd568e4 100644 --- a/scripts/vim/syntax/taskrc.vim +++ b/scripts/vim/syntax/taskrc.vim @@ -132,6 +132,7 @@ syn match taskrcGoodKey '^\s*\Vexpressions='he=e-1 syn match taskrcGoodKey '^\s*\Vextensions='he=e-1 syn match taskrcGoodKey '^\s*\Vfontunderline='he=e-1 syn match taskrcGoodKey '^\s*\Vgc='he=e-1 +syn match taskrcGoodKey '^\s*\Vpurge.on-sync='he=e-1 syn match taskrcGoodKey '^\s*\Vhooks='he=e-1 syn match taskrcGoodKey '^\s*\Vhooks.location='he=e-1 syn match taskrcGoodKey '^\s*\Vhyphenate='he=e-1 @@ -142,6 +143,7 @@ syn match taskrcGoodKey '^\s*\Vjournal.time='he=e-1 syn match taskrcGoodKey '^\s*\Vjournal.time.start.annotation='he=e-1 syn match taskrcGoodKey '^\s*\Vjournal.time.stop.annotation='he=e-1 syn match taskrcGoodKey '^\s*\Vjson.array='he=e-1 +syn match taskrcGoodKey '^\s*\Vlimit='he=e-1 syn match taskrcGoodKey '^\s*\Vlist.all.projects='he=e-1 syn match taskrcGoodKey '^\s*\Vlist.all.tags='he=e-1 syn match taskrcGoodKey '^\s*\Vlocale='he=e-1 @@ -163,10 +165,9 @@ syn match taskrcGoodKey '^\s*\Vrule.precedence.color='he=e-1 syn match taskrcGoodKey '^\s*\Vsearch.case.sensitive='he=e-1 syn match taskrcGoodKey '^\s*\Vsummary.all.projects='he=e-1 syn match taskrcGoodKey '^\s*\Vsugar='he=e-1 -syn match taskrcGoodKey '^\s*\Vsync.\(server.\(origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1 +syn match taskrcGoodKey '^\s*\Vsync.\(server.\(url\|origin\|client_id\|encryption_secret\)\|local.server_dir\)='he=e-1 syn match taskrcGoodKey '^\s*\Vtag.indicator='he=e-1 syn match taskrcGoodKey '^\s*\Vuda.\S\{-}.\(default\|type\|label\|values\|indicator\)='he=e-1 -syn match taskrcGoodKey '^\s*\Vundo.style='he=e-1 syn match taskrcGoodKey '^\s*\Vurgency.active.coefficient='he=e-1 syn match taskrcGoodKey '^\s*\Vurgency.age.coefficient='he=e-1 syn match taskrcGoodKey '^\s*\Vurgency.age.max='he=e-1 diff --git a/scripts/zsh/_task b/scripts/zsh/_task index fd8d0d278..013e2065d 100644 --- a/scripts/zsh/_task +++ b/scripts/zsh/_task @@ -31,7 +31,7 @@ _task_filter() { local word=$'[^\0]#\0' # projects - local _task_projects=($(task _projects)) + local _task_projects=($(task rc.hooks=0 _projects)) local task_projects=( /"$word"/ ":values:task projects:compadd -a _task_projects" @@ -157,7 +157,7 @@ _task_filter() { local uda_name uda_label uda_values local -a udas_spec task _udas | while read uda_name; do - uda_label="$(task _get rc.uda."$uda_name".label)" + uda_label="$(task rc.hooks=0 _get rc.uda."$uda_name".label)" # TODO: we could have got the values of every uda and try to complete that # but that can become extremly slow with a lot of udas #uda_values=(${(@s:,:)"$(task _get rc.uda."$uda_name".values)"}) @@ -167,8 +167,8 @@ _task_filter() { _regex_words -t ':' default 'task attributes' "${_task_all_attributes[@]}" local task_attributes=("$reply[@]") - local _task_tags=($(task _tags)) - local _task_config=($(task _config)) + local _task_tags=($(task rc.hooks=0 _tags)) + local _task_config=($(task rc.hooks=0 _config)) local _task_modifiers=( 'before' 'after' @@ -213,12 +213,12 @@ _task_filter() { # id-only completion (( $+functions[_task_ids] )) || _task_ids() { - local _ids=( ${(f)"$(task _zshids)"} ) + local _ids=( ${(f)"$(task rc.hooks=0 _zshids)"} ) _describe 'task ids' _ids } (( $+functions[_task_aliases] )) || _task_aliases() { - local _aliases=( ${(f)"$(task _aliases)"} ) + local _aliases=( ${(f)"$(task rc.hooks=0 _aliases)"} ) _describe 'task aliases' _aliases } @@ -230,7 +230,7 @@ _task_subcommands() { local cmd category desc local lastcategory='' # The list is sorted by category, in the right order. - local _task_zshcmds=( ${(f)"$(task _zshcommands)"} sentinel:sentinel:sentinel ) + local _task_zshcmds=( ${(f)"$(task rc.hooks=0 _zshcommands)"} sentinel:sentinel:sentinel ) for _zshcmd in "$_task_zshcmds[@]"; do # Parse out the three fields cmd=${_zshcmd%%:*} @@ -254,7 +254,7 @@ _task_subcommands() { ## contexts (( $+functions[_task_context] )) || _task_context() { - local _contexts=(${(f)"$(task _context)"}) + local _contexts=(${(f)"$(task rc.hooks=0 _context)"}) _describe 'task contexts' _contexts } @@ -264,7 +264,7 @@ _task_default() { local cmd ret=1 integer i=1 - local _task_cmds=($(task _commands; task _aliases)) + local _task_cmds=($(task rc.hooks=0 _commands; task _aliases)) while (( i < $#words )) do cmd="${_task_cmds[(r)$words[$i]]}" diff --git a/src/.gitignore b/src/.gitignore index 99a78c14e..1153e9327 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,6 +1 @@ -*.o -Makefile.in -debug -calc -lex liblibshared.a diff --git a/src/CLI2.cpp b/src/CLI2.cpp index 3fd286ecd..f951b3c9e 100644 --- a/src/CLI2.cpp +++ b/src/CLI2.cpp @@ -25,19 +25,21 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include -#include -#include -#include #include #include +#include +#include +#include +#include +#include +#include #include +#include +#include // Overridden by rc.abbreviation.minimum. int CLI2::minimumMatchLength = 3; @@ -46,298 +48,253 @@ int CLI2::minimumMatchLength = 3; static int safetyValveDefault = 10; //////////////////////////////////////////////////////////////////////////////// -A2::A2 (const std::string& raw, Lexer::Type lextype) -{ +A2::A2(const std::string& raw, Lexer::Type lextype) { _lextype = lextype; - attribute ("raw", raw); + attribute("raw", raw); } //////////////////////////////////////////////////////////////////////////////// -A2::A2 (const A2& other) = default; +A2::A2(const A2& other) = default; //////////////////////////////////////////////////////////////////////////////// -A2& A2::operator= (const A2& other) = default; +A2& A2::operator=(const A2& other) = default; //////////////////////////////////////////////////////////////////////////////// -bool A2::hasTag (const std::string& tag) const -{ - return std::find (_tags.begin (), _tags.end (), tag) != _tags.end (); +bool A2::hasTag(const std::string& tag) const { + return std::find(_tags.begin(), _tags.end(), tag) != _tags.end(); } //////////////////////////////////////////////////////////////////////////////// -void A2::tag (const std::string& tag) -{ - if (! hasTag (tag)) - _tags.push_back (tag); +void A2::tag(const std::string& tag) { + if (!hasTag(tag)) _tags.push_back(tag); } //////////////////////////////////////////////////////////////////////////////// -void A2::unTag (const std::string& tag) -{ - for (auto i = _tags.begin (); i != _tags.end (); ++i) - if (*i == tag) - { - _tags.erase (i); +void A2::unTag(const std::string& tag) { + for (auto i = _tags.begin(); i != _tags.end(); ++i) + if (*i == tag) { + _tags.erase(i); break; } } //////////////////////////////////////////////////////////////////////////////// // Accessor for attributes. -void A2::attribute (const std::string& name, const std::string& value) -{ +void A2::attribute(const std::string& name, const std::string& value) { _attributes[name] = value; - if (name == "raw") - decompose (); + if (name == "raw") decompose(); } //////////////////////////////////////////////////////////////////////////////// // Accessor for attributes. -const std::string A2::attribute (const std::string& name) const -{ +const std::string A2::attribute(const std::string& name) const { // Prevent autovivification. - auto i = _attributes.find (name); - if (i != _attributes.end ()) - return i->second; + auto i = _attributes.find(name); + if (i != _attributes.end()) return i->second; return ""; } //////////////////////////////////////////////////////////////////////////////// -const std::string A2::getToken () const -{ - auto i = _attributes.find ("canonical"); - if (i == _attributes.end ()) - i = _attributes.find ("raw"); +const std::string A2::getToken() const { + auto i = _attributes.find("canonical"); + if (i == _attributes.end()) i = _attributes.find("raw"); return i->second; } //////////////////////////////////////////////////////////////////////////////// -void A2::decompose () -{ - if (_lextype == Lexer::Type::tag) - { +void A2::decompose() { + if (_lextype == Lexer::Type::tag) { std::string raw = _attributes["raw"]; - attribute ("name", raw.substr (1)); - attribute ("sign", raw.substr (0, 1)); + attribute("name", raw.substr(1)); + attribute("sign", raw.substr(0, 1)); } - else if (_lextype == Lexer::Type::substitution) - { - //if (Directory (raw).exists ()) - // return; + else if (_lextype == Lexer::Type::substitution) { + // if (Directory (raw).exists ()) + // return; std::string from; std::string to; std::string flags; - if (Lexer::decomposeSubstitution (_attributes["raw"], from, to, flags)) - { - attribute ("from", from); - attribute ("to", to); - attribute ("flags", flags); + if (Lexer::decomposeSubstitution(_attributes["raw"], from, to, flags)) { + attribute("from", from); + attribute("to", to); + attribute("flags", flags); } } - else if (_lextype == Lexer::Type::pair) - { + else if (_lextype == Lexer::Type::pair) { std::string name; std::string mod; std::string sep; std::string value; - if (Lexer::decomposePair (_attributes["raw"], name, mod, sep, value)) - { - attribute ("name", name); - attribute ("modifier", mod); - attribute ("separator", sep); - attribute ("value", value); + if (Lexer::decomposePair(_attributes["raw"], name, mod, sep, value)) { + attribute("name", name); + attribute("modifier", mod); + attribute("separator", sep); + attribute("value", value); - if (name == "rc") - { + if (name == "rc") { if (mod != "") - tag ("CONFIG"); + tag("CONFIG"); else - tag ("RC"); + tag("RC"); } } } - else if (_lextype == Lexer::Type::pattern) - { - //if (Directory (raw).exists ()) - // return; + else if (_lextype == Lexer::Type::pattern) { + // if (Directory (raw).exists ()) + // return; std::string pattern; std::string flags; - if (Lexer::decomposePattern (_attributes["raw"], pattern, flags)) - { - attribute ("pattern", pattern); - attribute ("flags", flags); + if (Lexer::decomposePattern(_attributes["raw"], pattern, flags)) { + attribute("pattern", pattern); + attribute("flags", flags); } } } //////////////////////////////////////////////////////////////////////////////// -const std::string A2::dump () const -{ - auto output = Lexer::typeToString (_lextype); +const std::string A2::dump() const { + auto output = Lexer::typeToString(_lextype); // Dump attributes. std::string atts; - for (const auto& a : _attributes) - atts += a.first + "='\033[33m" + a.second + "\033[0m' "; + for (const auto& a : _attributes) atts += a.first + "='\033[33m" + a.second + "\033[0m' "; // Dump tags. std::string tags; - for (const auto& tag : _tags) - { - if (tag == "BINARY") tags += "\033[1;37;44m" + tag + "\033[0m "; - else if (tag == "CMD") tags += "\033[1;37;46m" + tag + "\033[0m "; - else if (tag == "FILTER") tags += "\033[1;37;42m" + tag + "\033[0m "; - else if (tag == "MODIFICATION") tags += "\033[1;37;43m" + tag + "\033[0m "; - else if (tag == "MISCELLANEOUS") tags += "\033[1;37;45m" + tag + "\033[0m "; - else if (tag == "RC") tags += "\033[1;37;41m" + tag + "\033[0m "; - else if (tag == "CONFIG") tags += "\033[1;37;101m" + tag + "\033[0m "; - else if (tag == "?") tags += "\033[38;5;255;48;5;232m" + tag + "\033[0m "; - else tags += "\033[32m" + tag + "\033[0m "; + for (const auto& tag : _tags) { + if (tag == "BINARY") + tags += "\033[1;37;44m" + tag + "\033[0m "; + else if (tag == "CMD") + tags += "\033[1;37;46m" + tag + "\033[0m "; + else if (tag == "FILTER") + tags += "\033[1;37;42m" + tag + "\033[0m "; + else if (tag == "MODIFICATION") + tags += "\033[1;37;43m" + tag + "\033[0m "; + else if (tag == "MISCELLANEOUS") + tags += "\033[1;37;45m" + tag + "\033[0m "; + else if (tag == "RC") + tags += "\033[1;37;41m" + tag + "\033[0m "; + else if (tag == "CONFIG") + tags += "\033[1;37;101m" + tag + "\033[0m "; + else if (tag == "?") + tags += "\033[38;5;255;48;5;232m" + tag + "\033[0m "; + else + tags += "\033[32m" + tag + "\033[0m "; } return output + ' ' + atts + tags; } //////////////////////////////////////////////////////////////////////////////// -static -const char* getValue (int argc, const char** argv, std::string arg) -{ - const auto is_arg = [&] (std::string s) - { - return s.size () > arg.size () + 1 - && (s[arg.size ()] == ':' || s[arg.size ()] == '=') - && s.compare (0, arg.size (), arg) == 0; +static const char* getValue(int argc, const char** argv, std::string arg) { + const auto is_arg = [&](std::string s) { + return s.size() > arg.size() + 1 && (s[arg.size()] == ':' || s[arg.size()] == '=') && + s.compare(0, arg.size(), arg) == 0; }; // find last argument before -- - auto last = std::make_reverse_iterator (argv); - auto first = std::make_reverse_iterator ( - std::find (argv, argv + argc, std::string ("--"))); - auto it = std::find_if (first, last, is_arg); - if (it == last) - return nullptr; + auto last = std::make_reverse_iterator(argv); + auto first = std::make_reverse_iterator(std::find(argv, argv + argc, std::string("--"))); + auto it = std::find_if(first, last, is_arg); + if (it == last) return nullptr; // return the string after : or = - return *it + arg.size () + 1; + return *it + arg.size() + 1; } //////////////////////////////////////////////////////////////////////////////// // Static method. -bool CLI2::getOverride (int argc, const char** argv, File& rc) -{ - const char* value = getValue (argc, argv, "rc"); - if (value == nullptr) - return false; - rc = File (value); +bool CLI2::getOverride(int argc, const char** argv, File& rc) { + const char* value = getValue(argc, argv, "rc"); + if (value == nullptr) return false; + rc = File(value); return true; } //////////////////////////////////////////////////////////////////////////////// // Look for CONFIG data.location and initialize a Path object. // Static method. -bool CLI2::getDataLocation (int argc, const char** argv, Path& data) -{ - const char* value = getValue (argc, argv, "rc.data.location"); - if (value == nullptr) - { - std::string location = Context::getContext ().config.get ("data.location"); - if (location != "") - data = location; +bool CLI2::getDataLocation(int argc, const char** argv, Path& data) { + const char* value = getValue(argc, argv, "rc.data.location"); + if (value == nullptr) { + std::string location = Context::getContext().config.get("data.location"); + if (location != "") data = location; return false; } - data = Directory (value); + data = Directory(value); return true; } //////////////////////////////////////////////////////////////////////////////// // Static method. -void CLI2::applyOverrides (int argc, const char** argv) -{ - auto& context = Context::getContext (); - auto last = std::find (argv, argv + argc, std::string ("--")); - auto is_override = [] (const std::string& s) - { - return s.compare (0, 3, "rc.") == 0; - }; - auto get_sep = [&] (const std::string& s) - { - if (is_override (s)) - return s.find_first_of (":=", 3); +void CLI2::applyOverrides(int argc, const char** argv) { + auto& context = Context::getContext(); + auto last = std::find(argv, argv + argc, std::string("--")); + auto is_override = [](const std::string& s) { return s.compare(0, 3, "rc.") == 0; }; + auto get_sep = [&](const std::string& s) { + if (is_override(s)) return s.find_first_of(":=", 3); return std::string::npos; }; - auto override_settings = [&] (std::string raw) - { - auto sep = get_sep (raw); - if (sep == std::string::npos) - return; - std::string name = raw.substr (3, sep - 3); - std::string value = raw.substr (sep + 1); - context.config.set (name, value); + auto override_settings = [&](std::string raw) { + auto sep = get_sep(raw); + if (sep == std::string::npos) return; + std::string name = raw.substr(3, sep - 3); + std::string value = raw.substr(sep + 1); + context.config.set(name, value); }; - auto display_overrides = [&] (std::string raw) - { - if (is_override (raw)) - context.footnote (format ("Configuration override {1}", raw)); + auto display_overrides = [&](std::string raw) { + if (is_override(raw)) context.footnote(format("Configuration override {1}", raw)); }; - std::for_each (argv, last, override_settings); - if (context.verbose ("override")) - std::for_each (argv, last, display_overrides); + std::for_each(argv, last, override_settings); + if (context.verbose("override")) std::for_each(argv, last, display_overrides); } //////////////////////////////////////////////////////////////////////////////// -void CLI2::alias (const std::string& name, const std::string& value) -{ - _aliases[name] = value; -} +void CLI2::alias(const std::string& name, const std::string& value) { _aliases[name] = value; } //////////////////////////////////////////////////////////////////////////////// -void CLI2::entity (const std::string& category, const std::string& name) -{ +void CLI2::entity(const std::string& category, const std::string& name) { // Walk the list of entities for category. - auto c = _entities.equal_range (category); + auto c = _entities.equal_range(category); for (auto e = c.first; e != c.second; ++e) - if (e->second == name) - return; + if (e->second == name) return; // The category/name pair was not found, therefore add it. - _entities.emplace (category, name); + _entities.emplace(category, name); } //////////////////////////////////////////////////////////////////////////////// // Capture a single argument. -void CLI2::add (const std::string& argument) -{ - A2 arg (Lexer::trim (argument), Lexer::Type::word); - arg.tag ("ORIGINAL"); - _original_args.push_back (arg); +void CLI2::add(const std::string& argument) { + A2 arg(Lexer::trim(argument), Lexer::Type::word); + arg.tag("ORIGINAL"); + _original_args.push_back(arg); // Adding a new argument invalidates prior analysis. - _args.clear (); + _args.clear(); } //////////////////////////////////////////////////////////////////////////////// // Capture a set of arguments, inserted immediately after arguments // after the binary.. -void CLI2::add (const std::vector & arguments, int offset /* = 0 */) -{ - std::vector replacement {_original_args.begin(), _original_args.begin() + offset + 1}; +void CLI2::add(const std::vector& arguments, int offset /* = 0 */) { + std::vector replacement{_original_args.begin(), _original_args.begin() + offset + 1}; - for (const auto& arg : arguments) - replacement.emplace_back (arg, Lexer::Type::word); + for (const auto& arg : arguments) replacement.emplace_back(arg, Lexer::Type::word); - for (unsigned int i = 1 + offset; i < _original_args.size (); ++i) - replacement.push_back (_original_args[i]); + for (unsigned int i = 1 + offset; i < _original_args.size(); ++i) + replacement.push_back(_original_args[i]); _original_args = replacement; // Adding a new argument invalidates prior analysis. - _args.clear (); + _args.clear(); } //////////////////////////////////////////////////////////////////////////////// @@ -347,31 +304,25 @@ void CLI2::add (const std::vector & arguments, int offset /* = 0 */ // The binary name is 'task', but if the binary is reported as 'cal' or // 'calendar' then it was invoked via symbolic link, in which case capture the // first argument as 'calendar'. -void CLI2::handleArg0 () -{ +void CLI2::handleArg0() { // Capture arg0 separately, because it is the command that was run, and could // need special handling. - auto raw = _original_args[0].attribute ("raw"); - A2 a (raw, Lexer::Type::word); - a.tag ("BINARY"); + auto raw = _original_args[0].attribute("raw"); + A2 a(raw, Lexer::Type::word); + a.tag("BINARY"); std::string basename = "task"; - auto slash = raw.rfind ('/'); - if (slash != std::string::npos) - basename = raw.substr (slash + 1); + auto slash = raw.rfind('/'); + if (slash != std::string::npos) basename = raw.substr(slash + 1); - a.attribute ("basename", basename); - if (basename == "cal" || - basename == "calendar") - { - _args.push_back (a); + a.attribute("basename", basename); + if (basename == "cal" || basename == "calendar") { + _args.push_back(a); - A2 cal ("calendar", Lexer::Type::word); - _args.push_back (cal); - } - else - { - _args.push_back (a); + A2 cal("calendar", Lexer::Type::word); + _args.push_back(cal); + } else { + _args.push_back(a); } } @@ -381,61 +332,53 @@ void CLI2::handleArg0 () // // As a side effect, tags all arguments after a terminator ('--') with // TERMINATED. -void CLI2::lexArguments () -{ +void CLI2::lexArguments() { // Note: Starts iterating at index 1, because ::handleArg0 has already // processed it. bool terminated = false; - for (unsigned int i = 1; i < _original_args.size (); ++i) - { - bool quoted = Lexer::wasQuoted (_original_args[i].attribute ("raw")); + for (unsigned int i = 1; i < _original_args.size(); ++i) { + bool quoted = Lexer::wasQuoted(_original_args[i].attribute("raw")); // Process single-token arguments. std::string lexeme; Lexer::Type type; - Lexer lex (_original_args[i].attribute ("raw")); - if (lex.token (lexeme, type) && - (lex.isEOS () || // Token goes to EOS - (quoted && type == Lexer::Type::pair))) // Quoted pairs automatically go to EOS + Lexer lex(_original_args[i].attribute("raw")); + if (lex.token(lexeme, type) && + (lex.isEOS() || // Token goes to EOS + (quoted && type == Lexer::Type::pair))) // Quoted pairs automatically go to EOS { - if (! terminated && type == Lexer::Type::separator) + if (!terminated && type == Lexer::Type::separator) terminated = true; else if (terminated) type = Lexer::Type::word; - A2 a (_original_args[i].attribute ("raw"), type); - if (terminated) - a.tag ("TERMINATED"); - if (quoted) - a.tag ("QUOTED"); + A2 a(_original_args[i].attribute("raw"), type); + if (terminated) a.tag("TERMINATED"); + if (quoted) a.tag("QUOTED"); - if (_original_args[i].hasTag ("ORIGINAL")) - a.tag ("ORIGINAL"); + if (_original_args[i].hasTag("ORIGINAL")) a.tag("ORIGINAL"); - _args.push_back (a); + _args.push_back(a); } // Process multiple-token arguments. - else - { + else { const std::string quote = "'"; // Escape unescaped single quotes std::string escaped = ""; // For performance reasons. The escaped string is as long as the original. - escaped.reserve (_original_args[i].attribute ("raw").size ()); + escaped.reserve(_original_args[i].attribute("raw").size()); std::string::size_type cursor = 0; bool nextEscaped = false; - while (int num = utf8_next_char (_original_args[i].attribute ("raw"), cursor)) - { - std::string character = utf8_character (num); + while (int num = utf8_next_char(_original_args[i].attribute("raw"), cursor)) { + std::string character = utf8_character(num); if (!nextEscaped && (character == "\\")) nextEscaped = true; else { - if (character == quote && !nextEscaped) - escaped += "\\"; + if (character == quote && !nextEscaped) escaped += "\\"; nextEscaped = false; } escaped += character; @@ -443,166 +386,141 @@ void CLI2::lexArguments () cursor = 0; std::string word; - if (Lexer::readWord (quote + escaped + quote, quote, cursor, word)) - { - Lexer::dequote (word); - A2 unknown (word, Lexer::Type::word); - if (lex.wasQuoted (_original_args[i].attribute ("raw"))) - unknown.tag ("QUOTED"); + if (Lexer::readWord(quote + escaped + quote, quote, cursor, word)) { + Lexer::dequote(word); + A2 unknown(word, Lexer::Type::word); + if (lex.wasQuoted(_original_args[i].attribute("raw"))) unknown.tag("QUOTED"); - if (_original_args[i].hasTag ("ORIGINAL")) - unknown.tag ("ORIGINAL"); + if (_original_args[i].hasTag("ORIGINAL")) unknown.tag("ORIGINAL"); - _args.push_back (unknown); + _args.push_back(unknown); } // This branch may have no use-case. - else - { - A2 unknown (_original_args[i].attribute ("raw"), Lexer::Type::word); - unknown.tag ("UNKNOWN"); + else { + A2 unknown(_original_args[i].attribute("raw"), Lexer::Type::word); + unknown.tag("UNKNOWN"); - if (lex.wasQuoted (_original_args[i].attribute ("raw"))) - unknown.tag ("QUOTED"); + if (lex.wasQuoted(_original_args[i].attribute("raw"))) unknown.tag("QUOTED"); - if (_original_args[i].hasTag ("ORIGINAL")) - unknown.tag ("ORIGINAL"); + if (_original_args[i].hasTag("ORIGINAL")) unknown.tag("ORIGINAL"); - _args.push_back (unknown); + _args.push_back(unknown); } } } - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze lexArguments")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze lexArguments")); } //////////////////////////////////////////////////////////////////////////////// // [1] Scan all args for the 'add' and 'log' commands, and demote any // Lexer::Type::Tag args with sign '-' to Lexer::Type::word. // [2] Convert any pseudo args name:value into config settings, and erase. -void CLI2::demotion () -{ +void CLI2::demotion() { bool changes = false; - std::vector replacement; + std::vector replacement; std::string canonical; - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::tag && - a.attribute ("sign") == "-") - { - std::string command = getCommand (); - if (command == "add" || - command == "log") - { + for (auto& a : _args) { + if (a._lextype == Lexer::Type::tag && a.attribute("sign") == "-") { + std::string command = getCommand(); + if (command == "add" || command == "log") { a._lextype = Lexer::Type::word; changes = true; } } else if (a._lextype == Lexer::Type::pair && - canonicalize (canonical, "pseudo", a.attribute ("name"))) - { - Context::getContext ().config.set (canonical, a.attribute ("value")); + canonicalize(canonical, "pseudo", a.attribute("name"))) { + Context::getContext().config.set(canonical, a.attribute("value")); changes = true; // Equivalent to erasing 'a'. continue; } - replacement.push_back (a); + replacement.push_back(a); } - if (changes && - Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze demotion")); + if (changes && Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze demotion")); } //////////////////////////////////////////////////////////////////////////////// // Intended to be called after ::add() to perform the final analysis. -void CLI2::analyze () -{ - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze")); +void CLI2::analyze() { + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze")); // Process _original_args. - _args.clear (); - handleArg0 (); - lexArguments (); + _args.clear(); + handleArg0(); + lexArguments(); // Process _args. - aliasExpansion (); - if (! findCommand ()) - { - defaultCommand (); - if (! findCommand ()) - throw std::string ("You must specify a command or a task to modify."); + aliasExpansion(); + if (!findCommand()) { + defaultCommand(); + if (!findCommand()) throw std::string("You must specify a command or a task to modify."); } - demotion (); - canonicalizeNames (); + demotion(); + canonicalizeNames(); // Determine arg types: FILTER, MODIFICATION, MISCELLANEOUS. - categorizeArgs (); - parenthesizeOriginalFilter (); + categorizeArgs(); + parenthesizeOriginalFilter(); // Cache frequently looked up items - _command = getCommand (); + _command = getCommand(); } //////////////////////////////////////////////////////////////////////////////// // Process raw filter string. // Insert filter arguments (wrapped in parentheses) immediatelly after the binary. -void CLI2::addFilter (const std::string& arg) -{ - if (arg.length ()) - { - std::vector filter; - filter.push_back ("("); +void CLI2::addFilter(const std::string& arg) { + if (arg.length()) { + std::vector filter; + filter.push_back("("); std::string lexeme; Lexer::Type type; - Lexer lex (arg); + Lexer lex(arg); - while (lex.token (lexeme, type)) - filter.push_back (lexeme); + while (lex.token(lexeme, type)) filter.push_back(lexeme); - filter.push_back (")"); - add (filter); - analyze (); + filter.push_back(")"); + add(filter); + analyze(); } } //////////////////////////////////////////////////////////////////////////////// // Process raw modification string. // Insert modification arguments immediatelly after the command (i.e. 'add') -void CLI2::addModifications (const std::string& arg) -{ - if (arg.length ()) - { - std::vector mods; +void CLI2::addModifications(const std::string& arg) { + if (arg.length()) { + std::vector mods; std::string lexeme; Lexer::Type type; - Lexer lex (arg); + Lexer lex(arg); - while (lex.token (lexeme, type)) - mods.push_back (lexeme); + while (lex.token(lexeme, type)) mods.push_back(lexeme); // Determine at which argument index does the task modification command // reside unsigned int cmdIndex = 0; - for (; cmdIndex < _args.size(); ++cmdIndex) - { + for (; cmdIndex < _args.size(); ++cmdIndex) { // Command found, stop iterating. - if (_args[cmdIndex].hasTag ("CMD")) - break; + if (_args[cmdIndex].hasTag("CMD")) break; } // Insert modifications after the command. - add (mods, cmdIndex); - analyze (); + add(mods, cmdIndex); + analyze(); } } @@ -610,35 +528,30 @@ void CLI2::addModifications (const std::string& arg) // There are situations where a context filter is applied. This method // determines whether one applies, and if so, applies it. Disqualifiers include: // - filter contains ID or UUID -void CLI2::addContext (bool readable, bool writeable) -{ +void CLI2::addContext(bool readable, bool writeable) { // Recursion block. - if (_context_added) - return; + if (_context_added) return; // Detect if any context is set, and bail out if not std::string contextString; if (readable) // Empty string is treated as "currently selected context" - contextString = Context::getContext ().getTaskContext("read", ""); + contextString = Context::getContext().getTaskContext("read", ""); else if (writeable) - contextString = Context::getContext ().getTaskContext("write", ""); + contextString = Context::getContext().getTaskContext("write", ""); else return; // If context is empty, bail out too - if (contextString.empty ()) - return; + if (contextString.empty()) return; // For readable contexts: Detect if UUID or ID is set, and bail out if (readable) - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::uuid || - a._lextype == Lexer::Type::number || - a._lextype == Lexer::Type::set) - { - Context::getContext ().debug (format ("UUID/ID argument found '{1}', not applying context.", a.attribute ("raw"))); + for (auto& a : _args) { + if (a._lextype == Lexer::Type::uuid || a._lextype == Lexer::Type::number || + a._lextype == Lexer::Type::set) { + Context::getContext().debug( + format("UUID/ID argument found '{1}', not applying context.", a.attribute("raw"))); return; } } @@ -647,75 +560,63 @@ void CLI2::addContext (bool readable, bool writeable) // block now, since addFilter calls analyze(), which calls addContext(). _context_added = true; if (readable) - addFilter (contextString); + addFilter(contextString); else if (writeable) - addModifications (contextString); + addModifications(contextString); // Inform the user about the application of context - if (Context::getContext ().verbose ("context")) - Context::getContext ().footnote (format ( - "Context '{1}' set. Use 'task context none' to remove.", - Context::getContext ().config.get ("context") - )); + if (Context::getContext().verbose("context")) + Context::getContext().footnote(format("Context '{1}' set. Use 'task context none' to remove.", + Context::getContext().config.get("context"))); } //////////////////////////////////////////////////////////////////////////////// // Parse the command line, identifiying filter components, expanding syntactic // sugar as necessary. -void CLI2::prepareFilter () -{ +void CLI2::prepareFilter() { // Clear and re-populate. - _id_ranges.clear (); - _uuid_list.clear (); + _id_ranges.clear(); + _uuid_list.clear(); _context_added = false; // Remove all the syntactic sugar for FILTERs. - lexFilterArgs (); - findIDs (); - findUUIDs (); - insertIDExpr (); - desugarFilterPlainArgs (); - findStrayModifications (); - desugarFilterTags (); - desugarFilterAttributes (); - desugarFilterPatterns (); - insertJunctions (); // Deliberately after all desugar calls. + lexFilterArgs(); + findIDs(); + findUUIDs(); + insertIDExpr(); + desugarFilterPlainArgs(); + findStrayModifications(); + desugarFilterTags(); + desugarFilterAttributes(); + desugarFilterPatterns(); + insertJunctions(); // Deliberately after all desugar calls. - if (Context::getContext ().verbose ("filter")) - { + if (Context::getContext().verbose("filter")) { std::string combined; - for (const auto& a : _args) - { - if (a.hasTag ("FILTER")) - { - if (combined != "") - combined += ' '; + for (const auto& a : _args) { + if (a.hasTag("FILTER")) { + if (combined != "") combined += ' '; - combined += a.attribute ("raw"); + combined += a.attribute("raw"); } } - if (combined.size ()) - Context::getContext ().footnote (std::string ("Filter: ") + combined); + if (combined.size()) Context::getContext().footnote(std::string("Filter: ") + combined); } } //////////////////////////////////////////////////////////////////////////////// // Return all the MISCELLANEOUS args as strings. -const std::vector CLI2::getWords () -{ - std::vector words; +const std::vector CLI2::getWords() { + std::vector words; for (const auto& a : _args) - if (a.hasTag ("MISCELLANEOUS")) - words.push_back (a.attribute ("raw")); + if (a.hasTag("MISCELLANEOUS")) words.push_back(a.attribute("raw")); - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - { - Color colorOrigArgs ("gray10 on gray4"); + if (Context::getContext().config.getInteger("debug.parser") >= 2) { + Color colorOrigArgs("gray10 on gray4"); std::string message = " "; - for (const auto& word : words) - message += colorOrigArgs.colorize (word) + ' '; - Context::getContext ().debug ("CLI2::getWords" + message); + for (const auto& word : words) message += colorOrigArgs.colorize(word) + ' '; + Context::getContext().debug("CLI2::getWords" + message); } return words; @@ -723,54 +624,45 @@ const std::vector CLI2::getWords () //////////////////////////////////////////////////////////////////////////////// // Return all the MISCELLANEOUS args. -const std::vector CLI2::getMiscellaneous () -{ - std::vector misc; +const std::vector CLI2::getMiscellaneous() { + std::vector misc; for (const auto& a : _args) - if (a.hasTag ("MISCELLANEOUS")) - misc.push_back (a); + if (a.hasTag("MISCELLANEOUS")) misc.push_back(a); return misc; } //////////////////////////////////////////////////////////////////////////////// // Search for 'value' in _entities category, return canonicalized value. -bool CLI2::canonicalize ( - std::string& canonicalized, - const std::string& category, - const std::string& value) -{ +bool CLI2::canonicalize(std::string& canonicalized, const std::string& category, + const std::string& value) { // Utilize a cache mapping of (category, value) -> canonicalized value. // This cache does not need to be invalidated, because entities are defined // only once per initialization of the Context object. - int cache_key = 31 * std::hash{} (category) + std::hash{} (value); - auto cache_result = _canonical_cache.find (cache_key); - if (cache_result != _canonical_cache.end()) - { + int cache_key = 31 * std::hash{}(category) + std::hash{}(value); + auto cache_result = _canonical_cache.find(cache_key); + if (cache_result != _canonical_cache.end()) { canonicalized = cache_result->second; return true; } // Extract a list of entities for category. - std::vector options; - auto c = _entities.equal_range (category); - for (auto e = c.first; e != c.second; ++e) - { + std::vector options; + auto c = _entities.equal_range(category); + for (auto e = c.first; e != c.second; ++e) { // Shortcut: if an exact match is found, success. - if (value == e->second) - { + if (value == e->second) { canonicalized = value; _canonical_cache[cache_key] = value; return true; } - options.push_back (e->second); + options.push_back(e->second); } // Match against the options, throw away results. - std::vector matches; - if (autoComplete (value, options, matches, minimumMatchLength) == 1) - { + std::vector matches; + if (autoComplete(value, options, matches, minimumMatchLength) == 1) { canonicalized = matches[0]; _canonical_cache[cache_key] = matches[0]; return true; @@ -780,186 +672,144 @@ bool CLI2::canonicalize ( } //////////////////////////////////////////////////////////////////////////////// -std::string CLI2::getBinary () const -{ - if (_args.size ()) - return _args[0].attribute ("raw"); +std::string CLI2::getBinary() const { + if (_args.size()) return _args[0].attribute("raw"); return ""; } //////////////////////////////////////////////////////////////////////////////// -std::string CLI2::getCommand (bool canonical) const -{ +std::string CLI2::getCommand(bool canonical) const { // Shortcut if analysis has been finalized - if (_command != "") - return _command; + if (_command != "") return _command; for (const auto& a : _args) - if (a.hasTag ("CMD")) - return a.attribute (canonical ? "canonical" : "raw"); + if (a.hasTag("CMD")) return a.attribute(canonical ? "canonical" : "raw"); return ""; } //////////////////////////////////////////////////////////////////////////////// -const std::string CLI2::dump (const std::string& title) const -{ +const std::string CLI2::dump(const std::string& title) const { std::stringstream out; out << "\033[1m" << title << "\033[0m\n" << " _original_args\n "; - Color colorArgs ("gray10 on gray4"); - Color colorFilter ("black on rgb311"); - for (auto i = _original_args.begin (); i != _original_args.end (); ++i) - { - if (i != _original_args.begin ()) - out << ' '; + Color colorArgs("gray10 on gray4"); + Color colorFilter("black on rgb311"); + for (auto i = _original_args.begin(); i != _original_args.end(); ++i) { + if (i != _original_args.begin()) out << ' '; - if (i->hasTag ("ORIGINAL")) - out << colorArgs.colorize (i->attribute ("raw")); + if (i->hasTag("ORIGINAL")) + out << colorArgs.colorize(i->attribute("raw")); else - out << colorFilter.colorize (i->attribute ("raw")); + out << colorFilter.colorize(i->attribute("raw")); } out << '\n'; - if (_args.size ()) - { + if (_args.size()) { out << " _args\n"; - for (const auto& a : _args) - out << " " << a.dump () << '\n'; + for (const auto& a : _args) out << " " << a.dump() << '\n'; } - if (_id_ranges.size ()) - { + if (_id_ranges.size()) { out << " _id_ranges\n "; - for (const auto& range : _id_ranges) - { + for (const auto& range : _id_ranges) { if (range.first != range.second) - out << colorArgs.colorize (range.first + "-" + range.second) << ' '; + out << colorArgs.colorize(range.first + "-" + range.second) << ' '; else - out << colorArgs.colorize (range.first) << ' '; + out << colorArgs.colorize(range.first) << ' '; } out << '\n'; } - if (_uuid_list.size ()) - { + if (_uuid_list.size()) { out << " _uuid_list\n "; - for (const auto& uuid : _uuid_list) - out << colorArgs.colorize (uuid) << ' '; + for (const auto& uuid : _uuid_list) out << colorArgs.colorize(uuid) << ' '; out << '\n'; } - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// // If any aliases are found in un-TERMINATED arguments, replace the alias with // a set of Lexed tokens from the configuration. -void CLI2::aliasExpansion () -{ +void CLI2::aliasExpansion() { bool changes = false; bool action; int counter = 0; - do - { + do { action = false; - std::vector reconstructed; + std::vector reconstructed; std::string raw; - for (const auto& i : _args) - { - raw = i.attribute ("raw"); - if (i.hasTag ("TERMINATED")) - { - reconstructed.push_back (i); - } - else if (_aliases.find (raw) != _aliases.end ()) - { + for (const auto& i : _args) { + raw = i.attribute("raw"); + if (i.hasTag("TERMINATED")) { + reconstructed.push_back(i); + } else if (_aliases.find(raw) != _aliases.end()) { std::string lexeme; Lexer::Type type; - Lexer lex (_aliases[raw]); - while (lex.token (lexeme, type)) - reconstructed.emplace_back (lexeme, type); + Lexer lex(_aliases[raw]); + while (lex.token(lexeme, type)) reconstructed.emplace_back(lexeme, type); action = true; changes = true; - } - else - { - reconstructed.push_back (i); + } else { + reconstructed.push_back(i); } } _args = reconstructed; - std::vector reconstructedOriginals; + std::vector reconstructedOriginals; bool terminated = false; - for (const auto& i : _original_args) - { - if (i.attribute ("raw") == "--") - terminated = true; + for (const auto& i : _original_args) { + if (i.attribute("raw") == "--") terminated = true; - if (terminated) - { - reconstructedOriginals.push_back (i); - } - else if (_aliases.find (i.attribute ("raw")) != _aliases.end ()) - { + if (terminated) { + reconstructedOriginals.push_back(i); + } else if (_aliases.find(i.attribute("raw")) != _aliases.end()) { std::string lexeme; Lexer::Type type; - Lexer lex (_aliases[i.attribute ("raw")]); - while (lex.token (lexeme, type)) - reconstructedOriginals.emplace_back (lexeme, type); + Lexer lex(_aliases[i.attribute("raw")]); + while (lex.token(lexeme, type)) reconstructedOriginals.emplace_back(lexeme, type); action = true; changes = true; - } - else - { - reconstructedOriginals.push_back (i); + } else { + reconstructedOriginals.push_back(i); } } _original_args = reconstructedOriginals; - } - while (action && counter++ < safetyValveDefault); + } while (action && counter++ < safetyValveDefault); if (counter >= safetyValveDefault) - Context::getContext ().debug (format ("Nested alias limit of {1} reached.", safetyValveDefault)); + Context::getContext().debug(format("Nested alias limit of {1} reached.", safetyValveDefault)); - if (changes && - Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze aliasExpansion")); + if (changes && Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze aliasExpansion")); } //////////////////////////////////////////////////////////////////////////////// // Scan all arguments and canonicalize names that need it. -void CLI2::canonicalizeNames () -{ +void CLI2::canonicalizeNames() { bool changes = false; - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::pair) - { - std::string raw = a.attribute ("raw"); - if (raw.substr (0, 3) != "rc:" && - raw.substr (0, 3) != "rc.") - { - std::string name = a.attribute ("name"); + for (auto& a : _args) { + if (a._lextype == Lexer::Type::pair) { + std::string raw = a.attribute("raw"); + if (raw.substr(0, 3) != "rc:" && raw.substr(0, 3) != "rc.") { + std::string name = a.attribute("name"); std::string canonical; - if (canonicalize (canonical, "pseudo", name) || - canonicalize (canonical, "attribute", name)) - { - a.attribute ("canonical", canonical); - } - else - { + if (canonicalize(canonical, "pseudo", name) || canonicalize(canonical, "attribute", name)) { + a.attribute("canonical", canonical); + } else { a._lextype = Lexer::Type::word; } @@ -968,18 +818,16 @@ void CLI2::canonicalizeNames () } } - if (changes && - Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze canonicalizeNames")); + if (changes && Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze canonicalizeNames")); } //////////////////////////////////////////////////////////////////////////////// // Categorize FILTER, MODIFICATION and MISCELLANEOUS args, based on CMD DNA. -void CLI2::categorizeArgs () -{ +void CLI2::categorizeArgs() { // Context is only applied for commands that request it. - std::string command = getCommand (); - Command* cmd = Context::getContext ().commands[command]; + std::string command = getCommand(); + Command* cmd = Context::getContext().commands[command]; // Determine if the command uses Context. CmdCustom and CmdTimesheet need to // be handled separately, as they override the parent Command::use_context @@ -988,35 +836,28 @@ void CLI2::categorizeArgs () // All Command classes overriding uses_context () getter need to be specified // here. bool uses_context; - if (dynamic_cast (cmd)) - uses_context = (dynamic_cast (cmd))->uses_context (); - else if (dynamic_cast (cmd)) - uses_context = (dynamic_cast (cmd))->uses_context (); + if (dynamic_cast(cmd)) + uses_context = (dynamic_cast(cmd))->uses_context(); + else if (dynamic_cast(cmd)) + uses_context = (dynamic_cast(cmd))->uses_context(); else if (cmd) - uses_context = cmd->uses_context (); + uses_context = cmd->uses_context(); // Apply the context, if applicable - if (cmd && uses_context) - addContext (cmd->accepts_filter (), cmd->accepts_modifications ()); + if (cmd && uses_context) addContext(cmd->accepts_filter(), cmd->accepts_modifications()); bool changes = false; bool afterCommand = false; - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::separator) - continue; + for (auto& a : _args) { + if (a._lextype == Lexer::Type::separator) continue; // Record that the command has been found, it affects behavior. - if (a.hasTag ("CMD")) - { + if (a.hasTag("CMD")) { afterCommand = true; } // Skip admin args. - else if (a.hasTag ("BINARY") || - a.hasTag ("RC") || - a.hasTag ("CONFIG")) - { + else if (a.hasTag("BINARY") || a.hasTag("RC") || a.hasTag("CONFIG")) { // NOP. } @@ -1031,83 +872,51 @@ void CLI2::categorizeArgs () // Fi Mo -- task [Fi] [Mo] // Fi Mo Mi Internally inconsistent // - else if (cmd && - ! cmd->accepts_filter () && - ! cmd->accepts_modifications () && - ! cmd->accepts_miscellaneous ()) - { + else if (cmd && !cmd->accepts_filter() && !cmd->accepts_modifications() && + !cmd->accepts_miscellaneous()) { // No commands were expected --> error. - throw format ("The '{1}' command does not allow '{2}'.", command, a.attribute ("raw")); - } - else if (cmd && - ! cmd->accepts_filter () && - ! cmd->accepts_modifications () && - cmd->accepts_miscellaneous ()) - { - a.tag ("MISCELLANEOUS"); + throw format("The '{1}' command does not allow '{2}'.", command, a.attribute("raw")); + } else if (cmd && !cmd->accepts_filter() && !cmd->accepts_modifications() && + cmd->accepts_miscellaneous()) { + a.tag("MISCELLANEOUS"); changes = true; - } - else if (cmd && - ! cmd->accepts_filter () && - cmd->accepts_modifications () && - ! cmd->accepts_miscellaneous ()) - { - a.tag ("MODIFICATION"); + } else if (cmd && !cmd->accepts_filter() && cmd->accepts_modifications() && + !cmd->accepts_miscellaneous()) { + a.tag("MODIFICATION"); changes = true; - } - else if (cmd && - ! cmd->accepts_filter () && - cmd->accepts_modifications () && - cmd->accepts_miscellaneous ()) - { + } else if (cmd && !cmd->accepts_filter() && cmd->accepts_modifications() && + cmd->accepts_miscellaneous()) { // Error: internally inconsistent. - throw std::string ("Unknown error. Please report."); - } - else if (cmd && - cmd->accepts_filter () && - ! cmd->accepts_modifications () && - ! cmd->accepts_miscellaneous ()) - { - a.tag ("FILTER"); + throw std::string("Unknown error. Please report."); + } else if (cmd && cmd->accepts_filter() && !cmd->accepts_modifications() && + !cmd->accepts_miscellaneous()) { + a.tag("FILTER"); changes = true; - } - else if (cmd && - cmd->accepts_filter () && - ! cmd->accepts_modifications () && - cmd->accepts_miscellaneous ()) - { + } else if (cmd && cmd->accepts_filter() && !cmd->accepts_modifications() && + cmd->accepts_miscellaneous()) { if (!afterCommand) - a.tag ("FILTER"); + a.tag("FILTER"); else - a.tag ("MISCELLANEOUS"); + a.tag("MISCELLANEOUS"); changes = true; - } - else if (cmd && - cmd->accepts_filter () && - cmd->accepts_modifications () && - ! cmd->accepts_miscellaneous ()) - { + } else if (cmd && cmd->accepts_filter() && cmd->accepts_modifications() && + !cmd->accepts_miscellaneous()) { if (!afterCommand) - a.tag ("FILTER"); + a.tag("FILTER"); else - a.tag ("MODIFICATION"); + a.tag("MODIFICATION"); changes = true; - } - else if (cmd && - cmd->accepts_filter () && - cmd->accepts_modifications () && - cmd->accepts_miscellaneous ()) - { + } else if (cmd && cmd->accepts_filter() && cmd->accepts_modifications() && + cmd->accepts_miscellaneous()) { // Error: internally inconsistent. - throw std::string ("Unknown error. Please report."); + throw std::string("Unknown error. Please report."); } } - if (changes && - Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze categorizeArgs")); + if (changes && Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze categorizeArgs")); } //////////////////////////////////////////////////////////////////////////////// @@ -1130,53 +939,43 @@ void CLI2::categorizeArgs () // task ( status:pending ) and ( +home or +work ) list // // the query is correct. -void CLI2::parenthesizeOriginalFilter () -{ +void CLI2::parenthesizeOriginalFilter() { // Locate the first and last ORIGINAL FILTER args. unsigned int firstOriginalFilter = 0; unsigned int lastOriginalFilter = 0; - for (unsigned int i = 1; i < _args.size (); ++i) - { - if (_args[i].hasTag ("FILTER") && - _args[i].hasTag ("ORIGINAL")) - { - if (firstOriginalFilter == 0) - firstOriginalFilter = i; + for (unsigned int i = 1; i < _args.size(); ++i) { + if (_args[i].hasTag("FILTER") && _args[i].hasTag("ORIGINAL")) { + if (firstOriginalFilter == 0) firstOriginalFilter = i; lastOriginalFilter = i; } } // If found, parenthesize the arg list accordingly. - if (firstOriginalFilter && - lastOriginalFilter) - { - std::vector reconstructed; - for (unsigned int i = 0; i < _args.size (); ++i) - { - if (i == firstOriginalFilter) - { - A2 openParen ("(", Lexer::Type::op); - openParen.tag ("ORIGINAL"); - openParen.tag ("FILTER"); - reconstructed.push_back (openParen); + if (firstOriginalFilter && lastOriginalFilter) { + std::vector reconstructed; + for (unsigned int i = 0; i < _args.size(); ++i) { + if (i == firstOriginalFilter) { + A2 openParen("(", Lexer::Type::op); + openParen.tag("ORIGINAL"); + openParen.tag("FILTER"); + reconstructed.push_back(openParen); } - reconstructed.push_back (_args[i]); + reconstructed.push_back(_args[i]); - if (i == lastOriginalFilter) - { - A2 closeParen (")", Lexer::Type::op); - closeParen.tag ("ORIGINAL"); - closeParen.tag ("FILTER"); - reconstructed.push_back (closeParen); + if (i == lastOriginalFilter) { + A2 closeParen(")", Lexer::Type::op); + closeParen.tag("ORIGINAL"); + closeParen.tag("FILTER"); + reconstructed.push_back(closeParen); } } _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze parenthesizeOriginalFilter")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze parenthesizeOriginalFilter")); } } @@ -1184,11 +983,9 @@ void CLI2::parenthesizeOriginalFilter () // Scan all arguments and if any are an exact match for a command name, then // tag as CMD. If an argument is an exact match for an attribute, despite being // an inexact match for a command, then it is not a command. -bool CLI2::findCommand () -{ - for (auto& a : _args) - { - std::string raw = a.attribute ("raw"); +bool CLI2::findCommand() { + for (auto& a : _args) { + std::string raw = a.attribute("raw"); std::string canonical; // If the arg canonicalized to a 'cmd', but is also not an exact match @@ -1196,28 +993,28 @@ bool CLI2::findCommand () // task project=foo list // ^cmd ^cmd // ^attribute - if (exactMatch ("cmd", raw)) + if (exactMatch("cmd", raw)) canonical = raw; - else if (exactMatch ("attribute", raw)) + else if (exactMatch("attribute", raw)) continue; - else if (! canonicalize (canonical, "cmd", raw)) + else if (!canonicalize(canonical, "cmd", raw)) continue; - a.attribute ("canonical", canonical); - a.tag ("CMD"); + a.attribute("canonical", canonical); + a.tag("CMD"); // Apply command DNA as tags. - Command* command = Context::getContext ().commands[canonical]; - if (command->read_only ()) a.tag ("READONLY"); - if (command->displays_id ()) a.tag ("SHOWSID"); - if (command->needs_gc ()) a.tag ("RUNSGC"); - if (command->uses_context ()) a.tag ("USESCONTEXT"); - if (command->accepts_filter ()) a.tag ("ALLOWSFILTER"); - if (command->accepts_modifications ()) a.tag ("ALLOWSMODIFICATIONS"); - if (command->accepts_miscellaneous ()) a.tag ("ALLOWSMISC"); + Command* command = Context::getContext().commands[canonical]; + if (command->read_only()) a.tag("READONLY"); + if (command->displays_id()) a.tag("SHOWSID"); + if (command->needs_gc()) a.tag("RUNSGC"); + if (command->uses_context()) a.tag("USESCONTEXT"); + if (command->accepts_filter()) a.tag("ALLOWSFILTER"); + if (command->accepts_modifications()) a.tag("ALLOWSMODIFICATIONS"); + if (command->accepts_miscellaneous()) a.tag("ALLOWSMISC"); - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze findCommand")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze findCommand")); // Stop and indicate command found. return true; @@ -1229,15 +1026,11 @@ bool CLI2::findCommand () //////////////////////////////////////////////////////////////////////////////// // Search for exact 'value' in _entities category. -bool CLI2::exactMatch ( - const std::string& category, - const std::string& value) const -{ +bool CLI2::exactMatch(const std::string& category, const std::string& value) const { // Extract a list of entities for category. - auto c = _entities.equal_range (category); + auto c = _entities.equal_range(category); for (auto e = c.first; e != c.second; ++e) - if (value == e->second) - return true; + if (value == e->second) return true; return false; } @@ -1245,90 +1038,74 @@ bool CLI2::exactMatch ( //////////////////////////////////////////////////////////////////////////////// // +tag --> tags _hastag_ tag // -tag --> tags _notag_ tag -void CLI2::desugarFilterTags () -{ +void CLI2::desugarFilterTags() { bool changes = false; - std::vector reconstructed; - for (const auto& a : _args) - { - if (a._lextype == Lexer::Type::tag && - a.hasTag ("FILTER")) - { + std::vector reconstructed; + for (const auto& a : _args) { + if (a._lextype == Lexer::Type::tag && a.hasTag("FILTER")) { changes = true; - A2 left ("tags", Lexer::Type::dom); - left.tag ("FILTER"); - reconstructed.push_back (left); + A2 left("tags", Lexer::Type::dom); + left.tag("FILTER"); + reconstructed.push_back(left); - std::string raw = a.attribute ("raw"); + std::string raw = a.attribute("raw"); - A2 op (raw[0] == '+' ? "_hastag_" : "_notag_", Lexer::Type::op); - op.tag ("FILTER"); - reconstructed.push_back (op); + A2 op(raw[0] == '+' ? "_hastag_" : "_notag_", Lexer::Type::op); + op.tag("FILTER"); + reconstructed.push_back(op); - A2 right ("" + raw.substr (1) + "", Lexer::Type::string); - right.tag ("FILTER"); - reconstructed.push_back (right); - } - else - reconstructed.push_back (a); + A2 right("" + raw.substr(1) + "", Lexer::Type::string); + right.tag("FILTER"); + reconstructed.push_back(right); + } else + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter desugarFilterTags")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter desugarFilterTags")); } } //////////////////////////////////////////////////////////////////////////////// -void CLI2::findStrayModifications () -{ +void CLI2::findStrayModifications() { bool changes = false; - auto command = getCommand (); - if (command == "add" || - command == "log") - { - for (auto& a : _args) - { - if (a.hasTag ("FILTER")) - { - a.unTag ("FILTER"); - a.tag ("MODIFICATION"); + auto command = getCommand(); + if (command == "add" || command == "log") { + for (auto& a : _args) { + if (a.hasTag("FILTER")) { + a.unTag("FILTER"); + a.tag("MODIFICATION"); changes = true; } } } if (changes) - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter findStrayModifications")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter findStrayModifications")); } //////////////////////////////////////////////////////////////////////////////// // [.]:['"][]['"] --> name value -void CLI2::desugarFilterAttributes () -{ +void CLI2::desugarFilterAttributes() { bool changes = false; - std::vector reconstructed; - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::pair && - a.hasTag ("FILTER")) - { - std::string raw = a.attribute ("raw"); - std::string name = a.attribute ("name"); - std::string mod = a.attribute ("modifier"); - std::string sep = a.attribute ("separator"); - std::string value = a.attribute ("value"); + std::vector reconstructed; + for (auto& a : _args) { + if (a._lextype == Lexer::Type::pair && a.hasTag("FILTER")) { + std::string raw = a.attribute("raw"); + std::string name = a.attribute("name"); + std::string mod = a.attribute("modifier"); + std::string sep = a.attribute("separator"); + std::string value = a.attribute("value"); // An unquoted string, while equivalent to an empty string, doesn't cause // an operand shortage in eval. - if (value == "") - value = "''"; + if (value == "") value = "''"; // Some values are expressions, which need to be lexed. The best way to // determine whether an expression is either a single value, or needs to @@ -1341,151 +1118,111 @@ void CLI2::desugarFilterAttributes () // 1d // ) // Use this sequence in place of a single value. - std::vector values = lexExpression (value); - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - { - Context::getContext ().debug ("CLI2::lexExpression " + name + ':' + value); - for (auto& v : values) - Context::getContext ().debug (" " + v.dump ()); - Context::getContext ().debug (" "); + std::vector values = lexExpression(value); + if (Context::getContext().config.getInteger("debug.parser") >= 2) { + Context::getContext().debug("CLI2::lexExpression " + name + ':' + value); + for (auto& v : values) Context::getContext().debug(" " + v.dump()); + Context::getContext().debug(" "); } bool found = false; std::string canonical; - if (canonicalize (canonical, "attribute", name)) - { + if (canonicalize(canonical, "attribute", name)) { // Certain attribute types do not suport math. // string --> no // numeric --> yes // date --> yes // duration --> yes bool evalSupported = true; - Column* col = Context::getContext ().columns[canonical]; - if (col && col->type () == "string") - evalSupported = false; + Column* col = Context::getContext().columns[canonical]; + if (col && col->type() == "string") evalSupported = false; - A2 lhs (name, Lexer::Type::dom); - lhs.tag ("FILTER"); - lhs.attribute ("canonical", canonical); - lhs.attribute ("modifier", mod); + A2 lhs(name, Lexer::Type::dom); + lhs.tag("FILTER"); + lhs.attribute("canonical", canonical); + lhs.attribute("modifier", mod); - A2 op ("", Lexer::Type::op); - op.tag ("FILTER"); + A2 op("", Lexer::Type::op); + op.tag("FILTER"); // Attribute types that do not support evaluation should be interpreted // as strings (currently this means that string attributes are not evaluated) - A2 rhs ("", evalSupported ? values[0]._lextype: Lexer::Type::string); - rhs.tag ("FILTER"); + A2 rhs("", evalSupported ? values[0]._lextype : Lexer::Type::string); + rhs.tag("FILTER"); // Special case for ':'. - if (mod == "") - { - op.attribute ("raw", "="); - rhs.attribute ("raw", value); - } - else if (mod == "before" || mod == "under" || mod == "below") - { - op.attribute ("raw", "<"); - rhs.attribute ("raw", value); - } - else if (mod == "after" || mod == "over" || mod == "above") - { - op.attribute ("raw", ">"); - rhs.attribute ("raw", value); - } - else if (mod == "by") - { - op.attribute ("raw", "<="); - rhs.attribute ("raw", value); - } - else if (mod == "none") - { - op.attribute ("raw", "=="); - rhs.attribute ("raw", "''"); - } - else if (mod == "any") - { - op.attribute ("raw", "!=="); - rhs.attribute ("raw", "''"); - } - else if (mod == "is" || mod == "equals") - { - op.attribute ("raw", "=="); - rhs.attribute ("raw", value); - } - else if (mod == "not") - { - op.attribute ("raw", "!="); - rhs.attribute ("raw", value); - } - else if (mod == "isnt") - { - op.attribute ("raw", "!=="); - rhs.attribute ("raw", value); - } - else if (mod == "has" || mod == "contains") - { - op.attribute ("raw", "~"); - rhs.attribute ("raw", value); - } - else if (mod == "hasnt") - { - op.attribute ("raw", "!~"); - rhs.attribute ("raw", value); - } - else if (mod == "startswith" || mod == "left") - { - op.attribute ("raw", "~"); - rhs.attribute ("raw", "^" + value); - } - else if (mod == "endswith" || mod == "right") - { - op.attribute ("raw", "~"); - rhs.attribute ("raw", value + "$"); - } - else if (mod == "word") - { - op.attribute ("raw", "~"); -#if defined (DARWIN) - rhs.attribute ("raw", value); -#elif defined (SOLARIS) - rhs.attribute ("raw", "\\<" + value + "\\>"); + if (mod == "") { + op.attribute("raw", "="); + rhs.attribute("raw", value); + } else if (mod == "before" || mod == "under" || mod == "below") { + op.attribute("raw", "<"); + rhs.attribute("raw", value); + } else if (mod == "after" || mod == "over" || mod == "above") { + op.attribute("raw", ">"); + rhs.attribute("raw", value); + } else if (mod == "by") { + op.attribute("raw", "<="); + rhs.attribute("raw", value); + } else if (mod == "none") { + op.attribute("raw", "=="); + rhs.attribute("raw", "''"); + } else if (mod == "any") { + op.attribute("raw", "!=="); + rhs.attribute("raw", "''"); + } else if (mod == "is" || mod == "equals") { + op.attribute("raw", "=="); + rhs.attribute("raw", value); + } else if (mod == "not") { + op.attribute("raw", "!="); + rhs.attribute("raw", value); + } else if (mod == "isnt") { + op.attribute("raw", "!=="); + rhs.attribute("raw", value); + } else if (mod == "has" || mod == "contains") { + op.attribute("raw", "~"); + rhs.attribute("raw", value); + } else if (mod == "hasnt") { + op.attribute("raw", "!~"); + rhs.attribute("raw", value); + } else if (mod == "startswith" || mod == "left") { + op.attribute("raw", "~"); + rhs.attribute("raw", "^" + value); + } else if (mod == "endswith" || mod == "right") { + op.attribute("raw", "~"); + rhs.attribute("raw", value + "$"); + } else if (mod == "word") { + op.attribute("raw", "~"); +#if defined(DARWIN) + rhs.attribute("raw", value); +#elif defined(SOLARIS) + rhs.attribute("raw", "\\<" + value + "\\>"); #else - rhs.attribute ("raw", "\\b" + value + "\\b"); + rhs.attribute("raw", "\\b" + value + "\\b"); #endif - } - else if (mod == "noword") - { - op.attribute ("raw", "!~"); -#if defined (DARWIN) - rhs.attribute ("raw", value); -#elif defined (SOLARIS) - rhs.attribute ("raw", "\\<" + value + "\\>"); + } else if (mod == "noword") { + op.attribute("raw", "!~"); +#if defined(DARWIN) + rhs.attribute("raw", value); +#elif defined(SOLARIS) + rhs.attribute("raw", "\\<" + value + "\\>"); #else - rhs.attribute ("raw", "\\b" + value + "\\b"); + rhs.attribute("raw", "\\b" + value + "\\b"); #endif - } - else - throw format ("Error: unrecognized attribute modifier '{1}'.", mod); + } else + throw format("Error: unrecognized attribute modifier '{1}'.", mod); - reconstructed.push_back (lhs); - reconstructed.push_back (op); + reconstructed.push_back(lhs); + reconstructed.push_back(op); // Do not modify this construct without full understanding. // Getting this wrong breaks a whole lot of filtering tests. - if (evalSupported) - { - for (auto& v : values) - reconstructed.push_back (v); - } - else if (Lexer::isDOM (rhs.attribute ("raw"))) - { + if (evalSupported) { + for (auto& v : values) reconstructed.push_back(v); + } else if (Lexer::isDOM(rhs.attribute("raw"))) { rhs._lextype = Lexer::Type::dom; - reconstructed.push_back (rhs); - } - else - { - reconstructed.push_back (rhs); + reconstructed.push_back(rhs); + } else { + reconstructed.push_back(rhs); } found = true; @@ -1494,66 +1231,58 @@ void CLI2::desugarFilterAttributes () // If the name does not canonicalize to either an attribute or a UDA // then it is not a recognized Lexer::Type::pair, so downgrade it to // Lexer::Type::word. - else - { + else { a._lextype = Lexer::Type::word; } if (found) changes = true; else - reconstructed.push_back (a); + reconstructed.push_back(a); } // Not a FILTER pair. else - reconstructed.push_back (a); + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter desugarFilterAttributes")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter desugarFilterAttributes")); } } //////////////////////////////////////////////////////////////////////////////// // /pattern/ --> description ~ 'pattern' -void CLI2::desugarFilterPatterns () -{ +void CLI2::desugarFilterPatterns() { bool changes = false; - std::vector reconstructed; - for (const auto& a : _args) - { - if (a._lextype == Lexer::Type::pattern && - a.hasTag ("FILTER")) - { + std::vector reconstructed; + for (const auto& a : _args) { + if (a._lextype == Lexer::Type::pattern && a.hasTag("FILTER")) { changes = true; - A2 lhs ("description", Lexer::Type::dom); - lhs.tag ("FILTER"); - reconstructed.push_back (lhs); + A2 lhs("description", Lexer::Type::dom); + lhs.tag("FILTER"); + reconstructed.push_back(lhs); - A2 op ("~", Lexer::Type::op); - op.tag ("FILTER"); - reconstructed.push_back (op); + A2 op("~", Lexer::Type::op); + op.tag("FILTER"); + reconstructed.push_back(op); - A2 rhs (a.attribute ("pattern"), Lexer::Type::string); - rhs.attribute ("flags", a.attribute ("flags")); - rhs.tag ("FILTER"); - reconstructed.push_back (rhs); - } - else - reconstructed.push_back (a); + A2 rhs(a.attribute("pattern"), Lexer::Type::string); + rhs.attribute("flags", a.attribute("flags")); + rhs.tag("FILTER"); + reconstructed.push_back(rhs); + } else + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter desugarFilterPatterns")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter desugarFilterPatterns")); } } @@ -1566,99 +1295,75 @@ void CLI2::desugarFilterPatterns () // a range: 5-10 // or a combination: 1,3,5-10 12 // -void CLI2::findIDs () -{ +void CLI2::findIDs() { bool changes = false; - if (Context::getContext ().config.getBoolean ("sugar")) - { + if (Context::getContext().config.getBoolean("sugar")) { bool previousFilterArgWasAnOperator = false; int filterCount = 0; - for (const auto& a : _args) - { - if (a.hasTag ("FILTER")) - { + for (const auto& a : _args) { + if (a.hasTag("FILTER")) { ++filterCount; - if (a._lextype == Lexer::Type::number) - { + if (a._lextype == Lexer::Type::number) { // Skip any number that was preceded by an operator. - if (! previousFilterArgWasAnOperator) - { + if (!previousFilterArgWasAnOperator) { changes = true; - std::string number = a.attribute ("raw"); - _id_ranges.emplace_back (number, number); + std::string number = a.attribute("raw"); + _id_ranges.emplace_back(number, number); } - } - else if (a._lextype == Lexer::Type::set) - { + } else if (a._lextype == Lexer::Type::set) { // Split the ID list into elements. - auto elements = split (a.attribute ("raw"), ','); + auto elements = split(a.attribute("raw"), ','); - for (auto& element : elements) - { + for (auto& element : elements) { changes = true; - auto hyphen = element.find ('-'); + auto hyphen = element.find('-'); if (hyphen != std::string::npos) - _id_ranges.emplace_back (element.substr (0, hyphen), element.substr (hyphen + 1)); + _id_ranges.emplace_back(element.substr(0, hyphen), element.substr(hyphen + 1)); else - _id_ranges.emplace_back (element, element); + _id_ranges.emplace_back(element, element); } } - std::string raw = a.attribute ("raw"); - previousFilterArgWasAnOperator = (a._lextype == Lexer::Type::op && - raw != "(" && - raw != ")") - ? true - : false; + std::string raw = a.attribute("raw"); + previousFilterArgWasAnOperator = + (a._lextype == Lexer::Type::op && raw != "(" && raw != ")") ? true : false; } } // If no IDs were found, and no filter was specified, look for number/set // listed as a MODIFICATION. - std::string command = getCommand (); + std::string command = getCommand(); - if (! _id_ranges.size () && - filterCount == 0 && - command != "add" && - command != "log") - { - for (auto& a : _args) - { - if (a.hasTag ("MODIFICATION")) - { - std::string raw = a.attribute ("raw"); + if (!_id_ranges.size() && filterCount == 0 && command != "add" && command != "log") { + for (auto& a : _args) { + if (a.hasTag("MODIFICATION")) { + std::string raw = a.attribute("raw"); // For a number to be an ID, it must not contain any sign or floating // point elements. - if (a._lextype == Lexer::Type::number && - raw.find ('.') == std::string::npos && - raw.find ('e') == std::string::npos && - raw.find ('-') == std::string::npos) - { + if (a._lextype == Lexer::Type::number && raw.find('.') == std::string::npos && + raw.find('e') == std::string::npos && raw.find('-') == std::string::npos) { changes = true; - a.unTag ("MODIFICATION"); - a.tag ("FILTER"); - _id_ranges.emplace_back (raw, raw); - } - else if (a._lextype == Lexer::Type::set) - { - a.unTag ("MODIFICATION"); - a.tag ("FILTER"); + a.unTag("MODIFICATION"); + a.tag("FILTER"); + _id_ranges.emplace_back(raw, raw); + } else if (a._lextype == Lexer::Type::set) { + a.unTag("MODIFICATION"); + a.tag("FILTER"); // Split the ID list into elements. - auto elements = split (raw, ','); + auto elements = split(raw, ','); - for (const auto& element : elements) - { + for (const auto& element : elements) { changes = true; - auto hyphen = element.find ('-'); + auto hyphen = element.find('-'); if (hyphen != std::string::npos) - _id_ranges.emplace_back (element.substr (0, hyphen), element.substr (hyphen + 1)); + _id_ranges.emplace_back(element.substr(0, hyphen), element.substr(hyphen + 1)); else - _id_ranges.emplace_back (element, element); + _id_ranges.emplace_back(element, element); } } } @@ -1667,117 +1372,89 @@ void CLI2::findIDs () } // Sugar-free. - else - { - std::vector reconstructed; - for (const auto& a : _args) - { - if (a.hasTag ("FILTER") && - a._lextype == Lexer::Type::number) - { + else { + std::vector reconstructed; + for (const auto& a : _args) { + if (a.hasTag("FILTER") && a._lextype == Lexer::Type::number) { changes = true; - A2 pair ("id:" + a.attribute ("raw"), Lexer::Type::pair); - pair.tag ("FILTER"); - pair.decompose (); - reconstructed.push_back (pair); - } - else - reconstructed.push_back (a); + A2 pair("id:" + a.attribute("raw"), Lexer::Type::pair); + pair.tag("FILTER"); + pair.decompose(); + reconstructed.push_back(pair); + } else + reconstructed.push_back(a); } - if (changes) - _args = reconstructed; + if (changes) _args = reconstructed; } if (changes) - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter findIDs")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter findIDs")); } //////////////////////////////////////////////////////////////////////////////// -void CLI2::findUUIDs () -{ +void CLI2::findUUIDs() { bool changes = false; - if (Context::getContext ().config.getBoolean ("sugar")) - { - for (const auto& a : _args) - { - if (a._lextype == Lexer::Type::uuid && - a.hasTag ("FILTER")) - { + if (Context::getContext().config.getBoolean("sugar")) { + for (const auto& a : _args) { + if (a._lextype == Lexer::Type::uuid && a.hasTag("FILTER")) { changes = true; - _uuid_list.push_back (a.attribute ("raw")); + _uuid_list.push_back(a.attribute("raw")); } } - if (! _uuid_list.size ()) - { - for (auto& a : _args) - { - if (a._lextype == Lexer::Type::uuid && - a.hasTag ("MODIFICATION")) - { + if (!_uuid_list.size()) { + for (auto& a : _args) { + if (a._lextype == Lexer::Type::uuid && a.hasTag("MODIFICATION")) { changes = true; - a.unTag ("MODIFICATION"); - a.tag ("FILTER"); - _uuid_list.push_back (a.attribute ("raw")); + a.unTag("MODIFICATION"); + a.tag("FILTER"); + _uuid_list.push_back(a.attribute("raw")); } } } } // Sugar-free. - else - { - std::vector reconstructed; - for (const auto& a : _args) - { - if (a.hasTag ("FILTER") && - a._lextype == Lexer::Type::uuid) - { + else { + std::vector reconstructed; + for (const auto& a : _args) { + if (a.hasTag("FILTER") && a._lextype == Lexer::Type::uuid) { changes = true; - A2 pair ("uuid:" + a.attribute ("raw"), Lexer::Type::pair); - pair.tag ("FILTER"); - pair.decompose (); - reconstructed.push_back (pair); - } - else - reconstructed.push_back (a); + A2 pair("uuid:" + a.attribute("raw"), Lexer::Type::pair); + pair.tag("FILTER"); + pair.decompose(); + reconstructed.push_back(pair); + } else + reconstructed.push_back(a); } - if (changes) - _args = reconstructed; + if (changes) _args = reconstructed; } if (changes) - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter findUUIDs")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter findUUIDs")); } //////////////////////////////////////////////////////////////////////////////// -void CLI2::insertIDExpr () -{ +void CLI2::insertIDExpr() { // Skip completely if no ID/UUID was found. This is because below, '(' and ')' // are inserted regardless of list size. - if (! _id_ranges.size () && - ! _uuid_list.size ()) - return; + if (!_id_ranges.size() && !_uuid_list.size()) return; // Find the *first* occurence of lexer type set/number/uuid, and replace it // with a synthesized expression. All other occurences are eaten. bool changes = false; bool foundID = false; - std::vector reconstructed; - for (const auto& a : _args) - { - if ((a._lextype == Lexer::Type::set || - a._lextype == Lexer::Type::number || + std::vector reconstructed; + for (const auto& a : _args) { + if ((a._lextype == Lexer::Type::set || a._lextype == Lexer::Type::number || a._lextype == Lexer::Type::uuid) && - a.hasTag ("FILTER")) - { - if (! foundID) - { + a.hasTag("FILTER")) { + if (!foundID) { foundID = true; changes = true; @@ -1797,146 +1474,136 @@ void CLI2::insertIDExpr () // ) // Building block operators. - A2 openParen ("(", Lexer::Type::op); openParen.tag ("FILTER"); - A2 closeParen (")", Lexer::Type::op); closeParen.tag ("FILTER"); - A2 opOr ("or", Lexer::Type::op); opOr.tag ("FILTER"); - A2 opAnd ("and", Lexer::Type::op); opAnd.tag ("FILTER"); - A2 opSimilar ("=", Lexer::Type::op); opSimilar.tag ("FILTER"); - A2 opEqual ("==", Lexer::Type::op); opEqual.tag ("FILTER"); - A2 opGTE (">=", Lexer::Type::op); opGTE.tag ("FILTER"); - A2 opLTE ("<=", Lexer::Type::op); opLTE.tag ("FILTER"); + A2 openParen("(", Lexer::Type::op); + openParen.tag("FILTER"); + A2 closeParen(")", Lexer::Type::op); + closeParen.tag("FILTER"); + A2 opOr("or", Lexer::Type::op); + opOr.tag("FILTER"); + A2 opAnd("and", Lexer::Type::op); + opAnd.tag("FILTER"); + A2 opSimilar("=", Lexer::Type::op); + opSimilar.tag("FILTER"); + A2 opEqual("==", Lexer::Type::op); + opEqual.tag("FILTER"); + A2 opGTE(">=", Lexer::Type::op); + opGTE.tag("FILTER"); + A2 opLTE("<=", Lexer::Type::op); + opLTE.tag("FILTER"); // Building block attributes. - A2 argID ("id", Lexer::Type::dom); - argID.tag ("FILTER"); + A2 argID("id", Lexer::Type::dom); + argID.tag("FILTER"); - A2 argUUID ("uuid", Lexer::Type::dom); - argUUID.tag ("FILTER"); + A2 argUUID("uuid", Lexer::Type::dom); + argUUID.tag("FILTER"); - reconstructed.push_back (openParen); + reconstructed.push_back(openParen); // Add all ID ranges. - for (auto r = _id_ranges.begin (); r != _id_ranges.end (); ++r) - { - if (r != _id_ranges.begin ()) - reconstructed.push_back (opOr); + for (auto r = _id_ranges.begin(); r != _id_ranges.end(); ++r) { + if (r != _id_ranges.begin()) reconstructed.push_back(opOr); - if (r->first == r->second) - { - reconstructed.push_back (openParen); - reconstructed.push_back (argID); - reconstructed.push_back (opEqual); + if (r->first == r->second) { + reconstructed.push_back(openParen); + reconstructed.push_back(argID); + reconstructed.push_back(opEqual); - A2 value (r->first, Lexer::Type::number); - value.tag ("FILTER"); - reconstructed.push_back (value); + A2 value(r->first, Lexer::Type::number); + value.tag("FILTER"); + reconstructed.push_back(value); - reconstructed.push_back (closeParen); - } - else - { + reconstructed.push_back(closeParen); + } else { bool ascending = true; - int low = strtol (r->first.c_str (), nullptr, 10); - int high = strtol (r->second.c_str (), nullptr, 10); + int low = strtol(r->first.c_str(), nullptr, 10); + int high = strtol(r->second.c_str(), nullptr, 10); if (low <= high) ascending = true; else ascending = false; - reconstructed.push_back (openParen); - reconstructed.push_back (argID); - reconstructed.push_back (opGTE); + reconstructed.push_back(openParen); + reconstructed.push_back(argID); + reconstructed.push_back(opGTE); - A2 startValue ((ascending ? r->first : r->second), Lexer::Type::number); - startValue.tag ("FILTER"); - reconstructed.push_back (startValue); + A2 startValue((ascending ? r->first : r->second), Lexer::Type::number); + startValue.tag("FILTER"); + reconstructed.push_back(startValue); - reconstructed.push_back (opAnd); - reconstructed.push_back (argID); - reconstructed.push_back (opLTE); + reconstructed.push_back(opAnd); + reconstructed.push_back(argID); + reconstructed.push_back(opLTE); - A2 endValue ((ascending ? r->second : r->first), Lexer::Type::number); - endValue.tag ("FILTER"); - reconstructed.push_back (endValue); + A2 endValue((ascending ? r->second : r->first), Lexer::Type::number); + endValue.tag("FILTER"); + reconstructed.push_back(endValue); - reconstructed.push_back (closeParen); + reconstructed.push_back(closeParen); } } // Combine the ID and UUID sections with 'or'. - if (_id_ranges.size () && - _uuid_list.size ()) - reconstructed.push_back (opOr); + if (_id_ranges.size() && _uuid_list.size()) reconstructed.push_back(opOr); // Add all UUID list items. - for (auto u = _uuid_list.begin (); u != _uuid_list.end (); ++u) - { - if (u != _uuid_list.begin ()) - reconstructed.push_back (opOr); + for (auto u = _uuid_list.begin(); u != _uuid_list.end(); ++u) { + if (u != _uuid_list.begin()) reconstructed.push_back(opOr); - reconstructed.push_back (openParen); - reconstructed.push_back (argUUID); - reconstructed.push_back (opSimilar); + reconstructed.push_back(openParen); + reconstructed.push_back(argUUID); + reconstructed.push_back(opSimilar); - A2 value (*u, Lexer::Type::string); - value.tag ("FILTER"); - reconstructed.push_back (value); + A2 value(*u, Lexer::Type::string); + value.tag("FILTER"); + reconstructed.push_back(value); - reconstructed.push_back (closeParen); + reconstructed.push_back(closeParen); } - reconstructed.push_back (closeParen); + reconstructed.push_back(closeParen); } // No 'else' because all set/number/uuid args but the first are removed. - } - else - reconstructed.push_back (a); + } else + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter insertIDExpr")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter insertIDExpr")); } } //////////////////////////////////////////////////////////////////////////////// // FILTER Lexer::Type::word args will become part of an expression, and so they // need to be Lexed. -void CLI2::lexFilterArgs () -{ +void CLI2::lexFilterArgs() { bool changes = false; - std::vector reconstructed; - for (const auto& a : _args) - { - if (a._lextype == Lexer::Type::word && - a.hasTag ("FILTER")) - { + std::vector reconstructed; + for (const auto& a : _args) { + if (a._lextype == Lexer::Type::word && a.hasTag("FILTER")) { changes = true; std::string lexeme; Lexer::Type type; - Lexer lex (a.attribute ("raw")); - while (lex.token (lexeme, type)) - { - A2 extra (lexeme, type); - extra.tag ("FILTER"); - reconstructed.push_back (extra); + Lexer lex(a.attribute("raw")); + while (lex.token(lexeme, type)) { + A2 extra(lexeme, type); + extra.tag("FILTER"); + reconstructed.push_back(extra); } - } - else - reconstructed.push_back (a); + } else + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter lexFilterArgs")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter lexFilterArgs")); } } @@ -1952,39 +1619,28 @@ void CLI2::lexFilterArgs () // Lexer::Type::identifier // Lexer::Type::date // -void CLI2::desugarFilterPlainArgs () -{ +void CLI2::desugarFilterPlainArgs() { // First walk the arg list looking for plain words that are not part of an // existing expression. auto prevprev = &_args[0]; auto prev = &_args[0]; - for (auto& a : _args) - { - auto raw = a.attribute ("raw"); - auto praw = prev->attribute ("raw"); - auto ppraw = prevprev->attribute ("raw"); + for (auto& a : _args) { + auto raw = a.attribute("raw"); + auto praw = prev->attribute("raw"); + auto ppraw = prevprev->attribute("raw"); - if ((prevprev->_lextype != Lexer::Type::op || // argX - ppraw == "(" || - ppraw == ")" || - ppraw == "and" || - ppraw == "or" || - ppraw == "xor") && + if ((prevprev->_lextype != Lexer::Type::op || // argX + ppraw == "(" || ppraw == ")" || ppraw == "and" || ppraw == "or" || ppraw == "xor") && (prev->_lextype == Lexer::Type::identifier || // candidate - prev->_lextype == Lexer::Type::date || // candidate - prev->_lextype == Lexer::Type::word) && // candidate + prev->_lextype == Lexer::Type::date || // candidate + prev->_lextype == Lexer::Type::word) && // candidate - prev->hasTag ("FILTER") && // candidate + prev->hasTag("FILTER") && // candidate - (a._lextype != Lexer::Type::op || // argY - raw == "(" || - raw == ")" || - raw == "and" || - raw == "or" || - raw == "xor")) - { - prev->tag ("PLAIN"); + (a._lextype != Lexer::Type::op || // argY + raw == "(" || raw == ")" || raw == "and" || raw == "or" || raw == "xor")) { + prev->tag("PLAIN"); } prevprev = prev; @@ -1992,61 +1648,53 @@ void CLI2::desugarFilterPlainArgs () } // Cover the case where the *last* argument is a plain arg. - auto& penultimate = _args[_args.size () - 2]; - auto praw = penultimate.attribute ("raw"); - auto& last = _args[_args.size () - 1]; - if ((penultimate._lextype != Lexer::Type::op || // argX - praw == "(" || - praw == ")" || - praw == "and" || - praw == "or" || - praw == "xor") && + auto& penultimate = _args[_args.size() - 2]; + auto praw = penultimate.attribute("raw"); + auto& last = _args[_args.size() - 1]; + if ((penultimate._lextype != Lexer::Type::op || // argX + praw == "(" || praw == ")" || praw == "and" || praw == "or" || praw == "xor") && - (last._lextype == Lexer::Type::identifier || // candidate - last._lextype == Lexer::Type::word) && // candidate + (last._lextype == Lexer::Type::identifier || // candidate + last._lextype == Lexer::Type::word) && // candidate - last.hasTag ("FILTER")) // candidate + last.hasTag("FILTER")) // candidate { - last.tag ("PLAIN"); + last.tag("PLAIN"); } // Walk the list again, upgrading PLAIN args. bool changes = false; - std::vector reconstructed; - for (const auto& a : _args) - { - if (a.hasTag ("PLAIN")) - { + std::vector reconstructed; + for (const auto& a : _args) { + if (a.hasTag("PLAIN")) { changes = true; - A2 lhs ("description", Lexer::Type::dom); - lhs.attribute ("canonical", "description"); - lhs.tag ("FILTER"); - lhs.tag ("PLAIN"); - reconstructed.push_back (lhs); + A2 lhs("description", Lexer::Type::dom); + lhs.attribute("canonical", "description"); + lhs.tag("FILTER"); + lhs.tag("PLAIN"); + reconstructed.push_back(lhs); - A2 op ("~", Lexer::Type::op); - op.tag ("FILTER"); - op.tag ("PLAIN"); - reconstructed.push_back (op); + A2 op("~", Lexer::Type::op); + op.tag("FILTER"); + op.tag("PLAIN"); + reconstructed.push_back(op); - std::string word = a.attribute ("raw"); - Lexer::dequote (word); - A2 rhs (word, Lexer::Type::string); - rhs.tag ("FILTER"); - rhs.tag ("PLAIN"); - reconstructed.push_back (rhs); - } - else - reconstructed.push_back (a); + std::string word = a.attribute("raw"); + Lexer::dequote(word); + A2 rhs(word, Lexer::Type::string); + rhs.tag("FILTER"); + rhs.tag("PLAIN"); + reconstructed.push_back(rhs); + } else + reconstructed.push_back(a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter desugarFilterPlainArgs")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter desugarFilterPlainArgs")); } } @@ -2060,13 +1708,11 @@ void CLI2::desugarFilterPlainArgs () // ( status = pending ) ( project = Home ) // ^ // it -----| => false -bool CLI2::isEmptyParenExpression (std::vector::iterator it, bool forward /* = true */) const -{ +bool CLI2::isEmptyParenExpression(std::vector::iterator it, bool forward /* = true */) const { int open = 0; int closed = 0; - for (auto a = it; a != (forward ? _args.end (): _args.begin()); (forward ? ++a: --a)) - { + for (auto a = it; a != (forward ? _args.end() : _args.begin()); (forward ? ++a : --a)) { if (a->attribute("raw") == "(") open++; else if (a->attribute("raw") == ")") @@ -2076,8 +1722,7 @@ bool CLI2::isEmptyParenExpression (std::vector::iterator it, bool forward /* return false; // Getting balanced parentheses means we have an empty paren expression - if (open == closed && open != 0) - return true; + if (open == closed && open != 0) return true; } // Should not end here. @@ -2093,39 +1738,28 @@ bool CLI2::isEmptyParenExpression (std::vector::iterator it, bool forward /* // ) ( --> ) and ( // --> and // -void CLI2::insertJunctions () -{ +void CLI2::insertJunctions() { bool changes = false; - std::vector reconstructed; - auto prev = _args.begin (); + std::vector reconstructed; + auto prev = _args.begin(); - for (auto a = _args.begin (); a != _args.end (); ++a) - { - if (a->hasTag ("FILTER")) - { + for (auto a = _args.begin(); a != _args.end(); ++a) { + if (a->hasTag("FILTER")) { // The prev iterator should be the first FILTER arg. - if (prev == _args.begin ()) - prev = a; + if (prev == _args.begin()) prev = a; // Insert AND between terms. - else if (a != prev) - { - if ((prev->_lextype != Lexer::Type::op && - a->attribute ("raw") == "(" && - ! isEmptyParenExpression(a, true) ) || - (prev->attribute ("raw") == ")" && - a->_lextype != Lexer::Type::op && - ! isEmptyParenExpression(prev, false)) || - (prev->attribute ("raw") == ")" && - a->attribute ("raw") == "(" && - ! isEmptyParenExpression(a, true) && - ! isEmptyParenExpression(prev, false)) || - (prev->_lextype != Lexer::Type::op && - a->_lextype != Lexer::Type::op)) - { - A2 opOr ("and", Lexer::Type::op); - opOr.tag ("FILTER"); - reconstructed.push_back (opOr); + else if (a != prev) { + if ((prev->_lextype != Lexer::Type::op && a->attribute("raw") == "(" && + !isEmptyParenExpression(a, true)) || + (prev->attribute("raw") == ")" && a->_lextype != Lexer::Type::op && + !isEmptyParenExpression(prev, false)) || + (prev->attribute("raw") == ")" && a->attribute("raw") == "(" && + !isEmptyParenExpression(a, true) && !isEmptyParenExpression(prev, false)) || + (prev->_lextype != Lexer::Type::op && a->_lextype != Lexer::Type::op)) { + A2 opOr("and", Lexer::Type::op); + opOr.tag("FILTER"); + reconstructed.push_back(opOr); changes = true; } } @@ -2134,15 +1768,14 @@ void CLI2::insertJunctions () prev = a; } - reconstructed.push_back (*a); + reconstructed.push_back(*a); } - if (changes) - { + if (changes) { _args = reconstructed; - if (Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::prepareFilter insertJunctions")); + if (Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::prepareFilter insertJunctions")); } } @@ -2155,78 +1788,65 @@ void CLI2::insertJunctions () // 2. If no command was found, but an ID/UUID was found, then assume a command // of 'information'. // -void CLI2::defaultCommand () -{ +void CLI2::defaultCommand() { // Scan the top-level branches for evidence of ID, UUID, overrides and other // arguments. - bool changes = false; - bool found_command = false; - bool found_sequence = false; + bool changes = false; + bool found_command = false; + bool found_sequence = false; - for (const auto& a : _args) - { - std::string raw = a.attribute ("raw"); + for (const auto& a : _args) { + std::string raw = a.attribute("raw"); - if (a.hasTag ("CMD")) - found_command = true; + if (a.hasTag("CMD")) found_command = true; - if (a._lextype == Lexer::Type::uuid || - a._lextype == Lexer::Type::number) - found_sequence = true; + if (a._lextype == Lexer::Type::uuid || a._lextype == Lexer::Type::number) found_sequence = true; } // If no command was specified, then a command will be inserted. - if (! found_command) - { + if (!found_command) { // Default command. - if (! found_sequence) - { + if (!found_sequence) { // Apply overrides, if any. - std::string defaultCommand = Context::getContext ().config.get ("default.command"); - if (defaultCommand != "") - { + std::string defaultCommand = Context::getContext().config.get("default.command"); + if (defaultCommand != "") { // Modify _args, _original_args to be: // [ ...] [...] - std::vector reconstructedOriginals {_original_args[0]}; - std::vector reconstructed {_args[0]}; + std::vector reconstructedOriginals{_original_args[0]}; + std::vector reconstructed{_args[0]}; std::string lexeme; Lexer::Type type; - Lexer lex (defaultCommand); + Lexer lex(defaultCommand); - while (lex.token (lexeme, type)) - { - reconstructedOriginals.emplace_back (lexeme, type); + while (lex.token(lexeme, type)) { + reconstructedOriginals.emplace_back(lexeme, type); - A2 cmd (lexeme, type); - cmd.tag ("DEFAULT"); - reconstructed.push_back (cmd); + A2 cmd(lexeme, type); + cmd.tag("DEFAULT"); + reconstructed.push_back(cmd); } - for (unsigned int i = 1; i < _original_args.size (); ++i) - reconstructedOriginals.push_back (_original_args[i]); + for (unsigned int i = 1; i < _original_args.size(); ++i) + reconstructedOriginals.push_back(_original_args[i]); - for (unsigned int i = 1; i < _args.size (); ++i) - reconstructed.push_back (_args[i]); + for (unsigned int i = 1; i < _args.size(); ++i) reconstructed.push_back(_args[i]); _original_args = reconstructedOriginals; _args = reconstructed; changes = true; } - } - else - { - A2 info ("information", Lexer::Type::word); - info.tag ("ASSUMED"); - _args.push_back (info); + } else { + A2 info("information", Lexer::Type::word); + info.tag("ASSUMED"); + _args.push_back(info); changes = true; } } - if (changes && - Context::getContext ().config.getInteger ("debug.parser") >= 2) - Context::getContext ().debug (dump ("CLI2::analyze defaultCommand")); + if (changes && Context::getContext().config.getInteger("debug.parser") >= 2) + Context::getContext().debug(dump("CLI2::analyze defaultCommand")); } //////////////////////////////////////////////////////////////////////////////// @@ -2240,30 +1860,27 @@ void CLI2::defaultCommand () // + // 1d // ) -std::vector CLI2::lexExpression (const std::string& expression) -{ - std::vector lexed; +std::vector CLI2::lexExpression(const std::string& expression) { + std::vector lexed; std::string lexeme; Lexer::Type type; - Lexer lex (expression); - while (lex.token (lexeme, type)) - { - A2 token (lexeme, type); - token.tag ("FILTER"); - lexed.push_back (token); + Lexer lex(expression); + while (lex.token(lexeme, type)) { + A2 token(lexeme, type); + token.tag("FILTER"); + lexed.push_back(token); } // If there were multiple tokens, parenthesize, because this expression will // be used as a value. - if (lexed.size () > 1) - { - A2 openParen ("(", Lexer::Type::op); - openParen.tag ("FILTER"); - A2 closeParen (")", Lexer::Type::op); - closeParen.tag ("FILTER"); + if (lexed.size() > 1) { + A2 openParen("(", Lexer::Type::op); + openParen.tag("FILTER"); + A2 closeParen(")", Lexer::Type::op); + closeParen.tag("FILTER"); - lexed.insert (lexed.begin (), openParen); - lexed.push_back (closeParen); + lexed.insert(lexed.begin(), openParen); + lexed.push_back(closeParen); } return lexed; diff --git a/src/CLI2.h b/src/CLI2.h index 6bada618a..ec446d688 100644 --- a/src/CLI2.h +++ b/src/CLI2.h @@ -26,100 +26,98 @@ #ifndef INCLUDED_CLI2 #define INCLUDED_CLI2 -#include -#include -#include -#include -#include #include +#include + +#include +#include +#include +#include // Represents a single argument. -class A2 -{ -public: - A2 (const std::string&, Lexer::Type); - A2 (const A2&); - A2& operator= (const A2&); - bool hasTag (const std::string&) const; - void tag (const std::string&); - void unTag (const std::string&); - void attribute (const std::string&, const std::string&); - const std::string attribute (const std::string&) const; - const std::string getToken () const; - const std::string dump () const; - void decompose (); +class A2 { + public: + A2(const std::string&, Lexer::Type); + A2(const A2&); + A2& operator=(const A2&); + bool hasTag(const std::string&) const; + void tag(const std::string&); + void unTag(const std::string&); + void attribute(const std::string&, const std::string&); + const std::string attribute(const std::string&) const; + const std::string getToken() const; + const std::string dump() const; + void decompose(); -public: - Lexer::Type _lextype {Lexer::Type::word}; - std::vector _tags {}; - std::map _attributes {}; + public: + Lexer::Type _lextype{Lexer::Type::word}; + std::vector _tags{}; + std::map _attributes{}; }; // Represents the command line. -class CLI2 -{ -public: +class CLI2 { + public: static int minimumMatchLength; - static bool getOverride (int, const char**, File&); - static bool getDataLocation (int, const char**, Path&); - static void applyOverrides (int, const char**); + static bool getOverride(int, const char**, File&); + static bool getDataLocation(int, const char**, Path&); + static void applyOverrides(int, const char**); -public: - CLI2 () = default; - void alias (const std::string&, const std::string&); - void entity (const std::string&, const std::string&); + public: + CLI2() = default; + void alias(const std::string&, const std::string&); + void entity(const std::string&, const std::string&); - void add (const std::string&); - void add (const std::vector &, int offset = 0); - void analyze (); - void addFilter (const std::string& arg); - void addModifications (const std::string& arg); - void addContext (bool readable, bool writeable); - void prepareFilter (); - const std::vector getWords (); - const std::vector getMiscellaneous (); - bool canonicalize (std::string&, const std::string&, const std::string&); - std::string getBinary () const; - std::string getCommand (bool canonical = true) const; - const std::string dump (const std::string& title = "CLI2 Parser") const; + void add(const std::string&); + void add(const std::vector&, int offset = 0); + void analyze(); + void addFilter(const std::string& arg); + void addModifications(const std::string& arg); + void addContext(bool readable, bool writeable); + void prepareFilter(); + const std::vector getWords(); + const std::vector getMiscellaneous(); + bool canonicalize(std::string&, const std::string&, const std::string&); + std::string getBinary() const; + std::string getCommand(bool canonical = true) const; + const std::string dump(const std::string& title = "CLI2 Parser") const; -private: - void handleArg0 (); - void lexArguments (); - void demotion (); - void aliasExpansion (); - void canonicalizeNames (); - void categorizeArgs (); - void parenthesizeOriginalFilter (); - bool findCommand (); - bool exactMatch (const std::string&, const std::string&) const; - void desugarFilterTags (); - void findStrayModifications (); - void desugarFilterAttributes (); - void desugarFilterPatterns (); - void findIDs (); - void findUUIDs (); - void insertIDExpr (); - void lexFilterArgs (); - bool isEmptyParenExpression (std::vector::iterator it, bool forward = true) const; - void desugarFilterPlainArgs (); - void insertJunctions (); - void defaultCommand (); - std::vector lexExpression (const std::string&); + private: + void handleArg0(); + void lexArguments(); + void demotion(); + void aliasExpansion(); + void canonicalizeNames(); + void categorizeArgs(); + void parenthesizeOriginalFilter(); + bool findCommand(); + bool exactMatch(const std::string&, const std::string&) const; + void desugarFilterTags(); + void findStrayModifications(); + void desugarFilterAttributes(); + void desugarFilterPatterns(); + void findIDs(); + void findUUIDs(); + void insertIDExpr(); + void lexFilterArgs(); + bool isEmptyParenExpression(std::vector::iterator it, bool forward = true) const; + void desugarFilterPlainArgs(); + void insertJunctions(); + void defaultCommand(); + std::vector lexExpression(const std::string&); -public: - std::multimap _entities {}; - std::map _aliases {}; - std::unordered_map _canonical_cache {}; - std::vector _original_args {}; - std::vector _args {}; + public: + std::multimap _entities{}; + std::map _aliases{}; + std::unordered_map _canonical_cache{}; + std::vector _original_args{}; + std::vector _args{}; - std::vector > _id_ranges {}; - std::vector _uuid_list {}; - std::string _command {""}; - bool _context_added {false}; + std::vector> _id_ranges{}; + std::vector _uuid_list{}; + std::string _command{""}; + bool _context_added{false}; }; #endif - diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9b095c45e..eaf5025e5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,25 +1,25 @@ cmake_minimum_required (VERSION 3.22) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/tc ${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/libshared/src - ${CMAKE_SOURCE_DIR}/taskchampion/lib ${TASK_INCLUDE_DIRS}) -add_library (task STATIC CLI2.cpp CLI2.h - Context.cpp Context.h - DOM.cpp DOM.h - Eval.cpp Eval.h - Filter.cpp Filter.h - Hooks.cpp Hooks.h - Lexer.cpp Lexer.h - TDB2.cpp TDB2.h - Task.cpp Task.h - Variant.cpp Variant.h - Version.cpp Version.h - ViewTask.cpp ViewTask.h +add_library (task STATIC CLI2.cpp + Context.cpp + DOM.cpp + Eval.cpp + Filter.cpp + Hooks.cpp + Lexer.cpp + Operation.cpp + TF2.cpp + TDB2.cpp + Task.cpp + Variant.cpp + Version.cpp + ViewTask.cpp dependency.cpp feedback.cpp legacy.cpp @@ -27,35 +27,34 @@ add_library (task STATIC CLI2.cpp CLI2.h recur.cpp rules.cpp sort.cpp - util.cpp util.h) + util.cpp) +target_link_libraries(task taskchampion-cpp) -add_library (libshared STATIC libshared/src/Color.cpp libshared/src/Color.h - libshared/src/Configuration.cpp libshared/src/Configuration.h - libshared/src/Datetime.cpp libshared/src/Datetime.h - libshared/src/Duration.cpp libshared/src/Duration.h - libshared/src/FS.cpp libshared/src/FS.h - libshared/src/JSON.cpp libshared/src/JSON.h - libshared/src/Msg.cpp libshared/src/Msg.h - libshared/src/Pig.cpp libshared/src/Pig.h - libshared/src/RX.cpp libshared/src/RX.h - libshared/src/Table.cpp libshared/src/Table.h - libshared/src/Timer.cpp libshared/src/Timer.h - libshared/src/format.cpp libshared/src/format.h +add_library (libshared STATIC libshared/src/Color.cpp + libshared/src/Configuration.cpp + libshared/src/Datetime.cpp + libshared/src/Duration.cpp + libshared/src/FS.cpp + libshared/src/JSON.cpp + libshared/src/Msg.cpp + libshared/src/Pig.cpp + libshared/src/RX.cpp + libshared/src/Table.cpp + libshared/src/Timer.cpp + libshared/src/format.cpp libshared/src/ip.cpp - libshared/src/shared.cpp libshared/src/shared.h - libshared/src/unicode.cpp libshared/src/unicode.h - libshared/src/utf8.cpp libshared/src/utf8.h - libshared/src/wcwidth.h) + libshared/src/shared.cpp + libshared/src/unicode.cpp + libshared/src/utf8.cpp) add_executable (task_executable main.cpp) add_executable (calc_executable calc.cpp) add_executable (lex_executable lex.cpp) # Yes, 'task' (and hence libshared) is included twice, otherwise linking fails on assorted OSes. -# Similarly for `tc`. -target_link_libraries (task_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES}) -target_link_libraries (calc_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES}) -target_link_libraries (lex_executable task tc commands tc columns libshared task libshared ${TASK_LIBRARIES}) +target_link_libraries (task_executable task commands columns libshared task libshared ${TASK_LIBRARIES}) +target_link_libraries (calc_executable task commands columns libshared task libshared ${TASK_LIBRARIES}) +target_link_libraries (lex_executable task commands columns libshared task libshared ${TASK_LIBRARIES}) if (DARWIN) # SystemConfiguration is required by Rust libraries like reqwest, to get proxy configuration. target_link_libraries (task_executable "-framework CoreFoundation -framework Security -framework SystemConfiguration") diff --git a/src/Context.cpp b/src/Context.cpp index ddfe9fd19..f3c87255b 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -25,31 +25,35 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include +#include +#include #include -#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include +#include #ifdef HAVE_COMMIT #include #endif -#include #include #ifdef SOLARIS @@ -58,417 +62,456 @@ //////////////////////////////////////////////////////////////////////////////// // This string is parsed and used as default values for configuration. -// Note: New configuration options should be added to the vim syntax file in scripts/vim/syntax/taskrc.vim +// Note: New configuration options should be added to the vim syntax file in +// scripts/vim/syntax/taskrc.vim std::string configurationDefaults = - "# Taskwarrior program configuration file.\n" - "# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',\n" - "# 'man task-sync' or 'man taskrc'\n" - "\n" - "# Here is an example of entries that use the default, override and blank values\n" - "# variable=foo -- By specifying a value, this overrides the default\n" - "# variable= -- By specifying no value, this means no default\n" - "# #variable=foo -- By commenting out the line, or deleting it, this uses the default\n" - "\n" - "# You can also refence environment variables:\n" - "# variable=$HOME/task\n" - "# variable=$VALUE\n" - "\n" - "# Use the command 'task show' to see all defaults and overrides\n" - "\n" - "# Files\n" - "data.location=~/.task\n" - "locking=1 # Use file-level locking\n" - "gc=1 # Garbage-collect data files - DO NOT CHANGE unless you are sure\n" - "exit.on.missing.db=0 # Whether to exit if ~/.task is not found\n" - "hooks=1 # Master control switch for hooks\n" - "\n" - "# Terminal\n" - "detection=1 # Detects terminal width\n" - "defaultwidth=80 # Without detection, assumed width\n" - "defaultheight=24 # Without detection, assumed height\n" - "avoidlastcolumn=0 # Fixes Cygwin width problem\n" - "hyphenate=1 # Hyphenates lines wrapped on non-word-breaks\n" - "#editor=vi # Preferred text editor\n" - "reserved.lines=1 # Assume a 1-line prompt\n" - "\n" - "# Miscellaneous\n" - "# verbose= # Comma-separated list. May contain any subset of:\n" - "# affected,blank,context,default,edit,filter,footnote,header,label,new-id,new-uuid,override,project,recur,special,sync\n" - "verbose=affected,blank,context,edit,header,footnote,label,new-id,project,special,sync,override,recur\n" - "confirmation=1 # Confirmation on delete, big changes\n" - "recurrence=1 # Enable recurrence\n" - "recurrence.confirmation=prompt # Confirmation for propagating changes among recurring tasks (yes/no/prompt)\n" - "allow.empty.filter=1 # An empty filter gets a warning and requires confirmation\n" - "indent.annotation=2 # Indent spaces for annotations\n" - "indent.report=0 # Indent spaces for whole report\n" - "row.padding=0 # Left and right padding for each row of report\n" - "column.padding=1 # Spaces between each column in a report\n" - "bulk=3 # 3 or more tasks considered a bulk change and is confirmed\n" - "nag=You have more urgent tasks. # Nag message to keep you honest\n" // TODO - "search.case.sensitive=1 # Setting to no allows case insensitive searches\n" - "active.indicator=* # What to show as an active task indicator\n" - "tag.indicator=+ # What to show as a tag indicator\n" - "dependency.indicator=D # What to show as a dependency indicator\n" - "recurrence.indicator=R # What to show as a task recurrence indicator\n" - "recurrence.limit=1 # Number of future recurring pending tasks\n" - "undo.style=side # Undo style - can be 'side', or 'diff'\n" - "regex=1 # Assume all search/filter strings are regexes\n" - "xterm.title=0 # Sets xterm title for some commands\n" - "expressions=infix # Prefer infix over postfix expressions\n" - "json.array=1 # Enclose JSON output in [ ]\n" - "abbreviation.minimum=2 # Shortest allowed abbreviation\n" - "news.version= # Latest version highlights read by the user\n" - "\n" - "# Dates\n" - "dateformat=Y-M-D # Preferred input and display date format\n" - "dateformat.holiday=YMD # Preferred input date format for holidays\n" - "dateformat.edit=Y-M-D H:N:S # Preferred display date format when editing\n" - "dateformat.info=Y-M-D H:N:S # Preferred display date format for information\n" - "dateformat.report= # Preferred display date format for reports\n" - "dateformat.annotation= # Preferred display date format for annotations\n" - "date.iso=1 # Enable ISO date support\n" - "weekstart=sunday # Sunday or Monday only\n" - "displayweeknumber=1 # Show week numbers on calendar\n" - "due=7 # Task is considered due in 7 days\n" - "\n" - "# Calendar controls\n" - "calendar.legend=1 # Display the legend on calendar\n" - "calendar.details=sparse # Calendar shows information for tasks w/due dates: full, sparse or none\n" - "calendar.details.report=list # Report to use when showing task information in cal\n" - "calendar.offset=0 # Apply an offset value to control the first month of the calendar\n" - "calendar.offset.value=-1 # The number of months the first month of the calendar is moved\n" - "calendar.holidays=none # Show public holidays on calendar:full, sparse or none\n" - "#calendar.monthsperline=3 # Number of calendar months on a line\n" - "\n" - "# Journal controls\n" - "journal.time=0 # Record start/stop commands as annotation\n" - "journal.time.start.annotation=Started task # Annotation description for the start journal entry\n" - "journal.time.stop.annotation=Stopped task # Annotation description for the stop journal entry\n" - "journal.info=1 # Display task journal with info command\n" - "\n" - "# Dependency controls\n" - "dependency.reminder=1 # Nags on dependency chain violations\n" - "dependency.confirmation=1 # Should dependency chain repair be confirmed?\n" - "\n" - "# Urgency Coefficients\n" - "urgency.user.tag.next.coefficient=15.0 # Urgency coefficient for 'next' special tag\n" - "urgency.due.coefficient=12.0 # Urgency coefficient for due dates\n" - "urgency.blocking.coefficient=8.0 # Urgency coefficient for blocking tasks\n" - "urgency.active.coefficient=4.0 # Urgency coefficient for active tasks\n" - "urgency.scheduled.coefficient=5.0 # Urgency coefficient for scheduled tasks\n" - "urgency.age.coefficient=2.0 # Urgency coefficient for age\n" - "urgency.annotations.coefficient=1.0 # Urgency coefficient for annotations\n" - "urgency.tags.coefficient=1.0 # Urgency coefficient for tags\n" - "urgency.project.coefficient=1.0 # Urgency coefficient for projects\n" - "urgency.blocked.coefficient=-5.0 # Urgency coefficient for blocked tasks\n" - "urgency.waiting.coefficient=-3.0 # Urgency coefficient for waiting status\n" - "urgency.inherit=0 # Recursively inherit highest urgency value from blocked tasks\n" - "urgency.age.max=365 # Maximum age in days\n" - "\n" - "#urgency.user.project.foo.coefficient=5.0 # Urgency coefficients for 'foo' project\n" - "#urgency.user.tag.foo.coefficient=5.0 # Urgency coefficients for 'foo' tag\n" - "#urgency.uda.foo.coefficient=5.0 # Urgency coefficients for UDA 'foo'\n" - "\n" - "# Color controls.\n" - "color=1 # Enable color\n" - "\n" - "# Here is the rule precedence order, highest to lowest.\n" - "# Note that these are just the color rule names, without the leading 'color.'\n" - "# and any trailing '.value'.\n" - "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due.today,due,blocked,blocking,recurring,tagged,uda.\n" - "\n" - "# General decoration\n" - "rule.color.merge=1\n" - "color.label=\n" - "color.label.sort=\n" - "color.alternate=on gray2\n" - "color.header=color3\n" - "color.footnote=color3\n" - "color.warning=bold red\n" - "color.error=white on red\n" - "color.debug=color4\n" - "\n" - "# Task state\n" - "color.completed=\n" - "color.deleted=\n" - "color.active=rgb555 on rgb410\n" - "color.recurring=rgb013\n" - "color.scheduled=on rgb001\n" - "color.until=\n" - "color.blocked=white on color8\n" - "color.blocking=black on color15\n" - "\n" - "# Project\n" - "color.project.none=\n" - "\n" - "# Priority UDA\n" - "color.uda.priority.H=color255\n" - "color.uda.priority.L=color245\n" - "color.uda.priority.M=color250\n" - "\n" - "# Tags\n" - "color.tag.next=rgb440\n" - "color.tag.none=\n" - "color.tagged=rgb031\n" - "\n" - "# Due\n" - "color.due.today=rgb400\n" - "color.due=color1\n" - "color.overdue=color9\n" - "\n" - "# Report: burndown\n" - "color.burndown.done=on rgb010\n" - "color.burndown.pending=on color9\n" - "color.burndown.started=on color11\n" - "\n" - "# Report: history\n" - "color.history.add=color0 on rgb500\n" - "color.history.delete=color0 on rgb550\n" - "color.history.done=color0 on rgb050\n" - "\n" - "# Report: summary\n" - "color.summary.background=white on color0\n" - "color.summary.bar=black on rgb141\n" - "\n" - "# Command: calendar\n" - "color.calendar.due.today=color15 on color1\n" - "color.calendar.due=color0 on color1\n" - "color.calendar.holiday=color0 on color11\n" - "color.calendar.scheduled=rgb013 on color15\n" - "color.calendar.overdue=color0 on color9\n" - "color.calendar.today=color15 on rgb013\n" - "color.calendar.weekend=on color235\n" - "color.calendar.weeknumber=rgb013\n" - "\n" - "# Command: sync\n" - "color.sync.added=rgb010\n" - "color.sync.changed=color11\n" - "color.sync.rejected=color9\n" - "\n" - "# Command: undo\n" - "color.undo.after=color2\n" - "color.undo.before=color1\n" - "\n" - "# UDA priority\n" - "uda.priority.type=string # UDA priority is a string type\n" - "uda.priority.label=Priority # UDA priority has a display label'\n" - "uda.priority.values=H,M,L, # UDA priority values are 'H', 'M', 'L' or ''\n" - " # UDA priority sorting is 'H' > 'M' > 'L' > '' (highest to lowest)\n" - "#uda.priority.default=M # UDA priority default value of 'M'\n" - "urgency.uda.priority.H.coefficient=6.0 # UDA priority coefficient for value 'H'\n" - "urgency.uda.priority.M.coefficient=3.9 # UDA priority coefficient for value 'M'\n" - "urgency.uda.priority.L.coefficient=1.8 # UDA priority coefficient for value 'L'\n" - "\n" - "#default.project=foo # Default project for 'add' command\n" - "#default.due=eom # Default due date for 'add' command\n" - "#default.scheduled=eom # Default scheduled date for 'add' command\n" - "default.command=next # When no arguments are specified\n" - "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and end.after:now-4wks )\n" - "\n" - "_forcecolor=0 # Forces color to be on, even for non TTY output\n" - "complete.all.tags=0 # Include old tag names in '_ags' command\n" - "list.all.projects=0 # Include old project names in 'projects' command\n" - "summary.all.projects=0 # Include old project names in 'summary' command\n" - "list.all.tags=0 # Include old tag names in 'tags' command\n" - "print.empty.columns=0 # Print columns which have no data for any task\n" - "debug=0 # Display diagnostics\n" - "sugar=1 # Syntactic sugar\n" - "obfuscate=0 # Obfuscate data for error reporting\n" - "fontunderline=1 # Uses underlines rather than -------\n" - "\n" - "# WARNING: Please read the documentation (man task-sync) before setting up\n" - "# Taskwarrior for Taskserver synchronization.\n" - "\n" - "#sync.encryption_secret # Encryption secret for sync to a server\n" - "#sync.server.client_id # Client ID for sync to a server\n" - "#sync.server.origin # Origin of the sync server\n" - "#sync.local.server_dir # Directory for local sync\n" - "#sync.gcp.credential_path # Path to JSON file containing credentials to authenticate GCP Sync\n" - "#sync.gcp.bucket # Bucket for sync to GCP\n" - "\n" - "# Aliases - alternate names for commands\n" - "alias.rm=delete # Alias for the delete command\n" - "alias.history=history.monthly # Prefer monthly over annual history reports\n" - "alias.ghistory=ghistory.monthly # Prefer monthly graphical over annual history reports\n" - "alias.burndown=burndown.weekly # Prefer the weekly burndown chart\n" - "\n" - "# Reports\n" - "\n" - "report.long.description=All details of tasks\n" - "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until,Description\n" - "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur,wait.remaining,scheduled,due,until,description\n" - "report.long.filter=status:pending -WAITING\n" - "report.long.sort=modified-\n" - "report.long.context=1\n" - "\n" - "report.list.description=Most details of tasks\n" - "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n" - "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur.indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n" - "report.list.filter=status:pending -WAITING\n" - "report.list.sort=start-,due+,project+,urgency-\n" - "report.list.context=1\n" - "\n" - "report.ls.description=Few details of tasks\n" - "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n" - "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n" - "report.ls.filter=status:pending -WAITING\n" - "report.ls.sort=start-,description+\n" - "report.ls.context=1\n" - "\n" - "report.minimal.description=Minimal details of tasks\n" - "report.minimal.labels=ID,Project,Tags,Description\n" - "report.minimal.columns=id,project,tags.count,description.count\n" - "report.minimal.filter=status:pending\n" - "report.minimal.sort=project+/,description+\n" - "report.minimal.context=1\n" - "\n" - "report.newest.description=Newest tasks\n" - "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n" - "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n" - "report.newest.filter=status:pending\n" - "report.newest.sort=entry-\n" - "report.newest.context=1\n" - "\n" - "report.oldest.description=Oldest tasks\n" - "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n" - "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority,project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n" - "report.oldest.filter=status:pending\n" - "report.oldest.sort=entry+\n" - "report.oldest.context=1\n" - "\n" - "report.overdue.description=Overdue tasks\n" - "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n" - "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator,scheduled.countdown,due,until,description,urgency\n" - "report.overdue.filter=status:pending and +OVERDUE\n" - "report.overdue.sort=urgency-,due+\n" - "report.overdue.context=1\n" - "\n" - "report.active.description=Active tasks\n" - "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until,Description\n" - "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags,recur,wait,scheduled.remaining,due,until,description\n" - "report.active.filter=status:pending and +ACTIVE\n" - "report.active.sort=project+,start+\n" - "report.active.context=1\n" - "\n" - "report.completed.description=Completed tasks\n" - "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n" - "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags,recur.indicator,due,description\n" - "report.completed.filter=status:completed\n" - "report.completed.sort=end+\n" - "report.completed.context=1\n" - "\n" - "report.recurring.description=Recurring Tasks\n" - "report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description,Urg\n" - "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short,project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n" - "report.recurring.filter=(status:pending and +CHILD) or (status:recurring and +PARENT)\n" - "report.recurring.sort=due+,urgency-,entry+\n" - "report.recurring.context=1\n" - "\n" - "report.waiting.description=Waiting (hidden) tasks\n" - "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n" - "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags,recur.indicator,wait,wait.remaining,scheduled,due,until,description\n" - "report.waiting.filter=+WAITING\n" - "report.waiting.sort=due+,wait+,entry+\n" - "report.waiting.context=1\n" - "\n" - "report.all.description=All tasks\n" - "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n" - "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends.indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled.remaining,due,until.remaining,description\n" - "report.all.sort=entry-\n" - "report.all.context=1\n" - "\n" - "report.next.description=Most urgent tasks\n" - "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n" - "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled.countdown,due.relative,until.remaining,description,urgency\n" - "report.next.filter=status:pending -WAITING limit:page\n" - "report.next.sort=urgency-\n" - "report.next.context=1\n" - "\n" - "report.ready.description=Most urgent actionable tasks\n" - "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n" - "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur.indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n" - "report.ready.filter=+READY\n" - "report.ready.sort=start-,urgency-\n" - "report.ready.context=1\n" - "\n" - "report.blocked.description=Blocked tasks\n" - "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n" - "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n" - "report.blocked.sort=due+,priority-,start-,project+\n" - "report.blocked.filter=status:pending -WAITING +BLOCKED\n" - "report.blocked.context=1\n" - "\n" - "report.unblocked.description=Unblocked tasks\n" - "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n" - "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n" - "report.unblocked.sort=due+,priority-,start-,project+\n" - "report.unblocked.filter=status:pending -WAITING -BLOCKED\n" - "report.unblocked.context=1\n" - "\n" - "report.blocking.description=Blocking tasks\n" - "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n" - "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled.remaining,due.relative,until.remaining,description.count,urgency\n" - "report.blocking.sort=urgency-,due+,entry+\n" - "report.blocking.filter=status:pending -WAITING +BLOCKING\n" - "report.blocking.context=1\n" - "\n" - "report.timesheet.filter=(+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks)\n" - "report.timesheet.context=0\n" - "\n"; + "# Taskwarrior program configuration file.\n" + "# For more documentation, see https://taskwarrior.org or try 'man task', 'man task-color',\n" + "# 'man task-sync' or 'man taskrc'\n" + "\n" + "# Here is an example of entries that use the default, override and blank values\n" + "# variable=foo -- By specifying a value, this overrides the default\n" + "# variable= -- By specifying no value, this means no default\n" + "# #variable=foo -- By commenting out the line, or deleting it, this uses the default\n" + "\n" + "# You can also refence environment variables:\n" + "# variable=$HOME/task\n" + "# variable=$VALUE\n" + "\n" + "# Use the command 'task show' to see all defaults and overrides\n" + "\n" + "# Files\n" + "data.location=~/.task\n" + "gc=1 # Garbage-collect data files - DO NOT CHANGE " + "unless you are sure\n" + "exit.on.missing.db=0 # Whether to exit if ~/.task is not found\n" + "hooks=1 # Master control switch for hooks\n" + "\n" + "# Terminal\n" + "detection=1 # Detects terminal width\n" + "defaultwidth=80 # Without detection, assumed width\n" + "defaultheight=24 # Without detection, assumed height\n" + "avoidlastcolumn=0 # Fixes Cygwin width problem\n" + "hyphenate=1 # Hyphenates lines wrapped on non-word-breaks\n" + "#editor=vi # Preferred text editor\n" + "reserved.lines=1 # Assume a 1-line prompt\n" + "\n" + "# Miscellaneous\n" + "# verbose= # Comma-separated list. May contain any " + "subset of:\n" + "# " + "affected,blank,context,default,edit,filter,footnote,header,label,new-id,new-uuid,news," + "override,project,recur,special,sync\n" + "verbose=affected,blank,context,edit,header,footnote,label,new-id,news,project,special,sync," + "override,recur\n" + "confirmation=1 # Confirmation on delete, big changes\n" + "recurrence=1 # Enable recurrence\n" + "recurrence.confirmation=prompt # Confirmation for propagating changes among " + "recurring tasks (yes/no/prompt)\n" + "allow.empty.filter=1 # An empty filter gets a warning and requires " + "confirmation\n" + "indent.annotation=2 # Indent spaces for annotations\n" + "indent.report=0 # Indent spaces for whole report\n" + "row.padding=0 # Left and right padding for each row of " + "report\n" + "column.padding=1 # Spaces between each column in a report\n" + "bulk=3 # 3 or more tasks considered a bulk change and " + "is confirmed\n" + "nag=You have more urgent tasks. # Nag message to keep you honest\n" // TODO + "search.case.sensitive=1 # Setting to no allows case insensitive " + "searches\n" + "active.indicator=* # What to show as an active task indicator\n" + "tag.indicator=+ # What to show as a tag indicator\n" + "dependency.indicator=D # What to show as a dependency indicator\n" + "recurrence.indicator=R # What to show as a task recurrence indicator\n" + "recurrence.limit=1 # Number of future recurring pending tasks\n" + "regex=1 # Assume all search/filter strings are " + "regexes\n" + "xterm.title=0 # Sets xterm title for some commands\n" + "expressions=infix # Prefer infix over postfix expressions\n" + "json.array=1 # Enclose JSON output in [ ]\n" + "abbreviation.minimum=2 # Shortest allowed abbreviation\n" + "news.version= # Latest version highlights read by the user\n" + "purge.on-sync=0 # Purge old tasks on sync\n" + "\n" + "# Dates\n" + "dateformat=Y-M-D # Preferred input and display date format\n" + "dateformat.holiday=YMD # Preferred input date format for holidays\n" + "dateformat.edit=Y-M-D H:N:S # Preferred display date format when editing\n" + "dateformat.info=Y-M-D H:N:S # Preferred display date format for " + "information\n" + "dateformat.report= # Preferred display date format for reports\n" + "dateformat.annotation= # Preferred display date format for " + "annotations\n" + "date.iso=1 # Enable ISO date support\n" + "weekstart=sunday # Sunday or Monday only\n" + "displayweeknumber=1 # Show week numbers on calendar\n" + "due=7 # Task is considered due in 7 days\n" + "\n" + "# Calendar controls\n" + "calendar.legend=1 # Display the legend on calendar\n" + "calendar.details=sparse # Calendar shows information for tasks w/due " + "dates: full, sparse or none\n" + "calendar.details.report=list # Report to use when showing task information " + "in cal\n" + "calendar.offset=0 # Apply an offset value to control the first " + "month of the calendar\n" + "calendar.offset.value=-1 # The number of months the first month of the " + "calendar is moved\n" + "calendar.holidays=none # Show public holidays on calendar:full, " + "sparse or none\n" + "#calendar.monthsperline=3 # Number of calendar months on a line\n" + "\n" + "# Journal controls\n" + "journal.time=0 # Record start/stop commands as annotation\n" + "journal.time.start.annotation=Started task # Annotation description for the start journal " + "entry\n" + "journal.time.stop.annotation=Stopped task # Annotation description for the stop journal " + "entry\n" + "journal.info=1 # Display task journal with info command\n" + "\n" + "# Dependency controls\n" + "dependency.reminder=1 # Nags on dependency chain violations\n" + "dependency.confirmation=1 # Should dependency chain repair be " + "confirmed?\n" + "\n" + "# Urgency Coefficients\n" + "urgency.user.tag.next.coefficient=15.0 # Urgency coefficient for 'next' special tag\n" + "urgency.due.coefficient=12.0 # Urgency coefficient for due dates\n" + "urgency.blocking.coefficient=8.0 # Urgency coefficient for blocking tasks\n" + "urgency.active.coefficient=4.0 # Urgency coefficient for active tasks\n" + "urgency.scheduled.coefficient=5.0 # Urgency coefficient for scheduled tasks\n" + "urgency.age.coefficient=2.0 # Urgency coefficient for age\n" + "urgency.annotations.coefficient=1.0 # Urgency coefficient for annotations\n" + "urgency.tags.coefficient=1.0 # Urgency coefficient for tags\n" + "urgency.project.coefficient=1.0 # Urgency coefficient for projects\n" + "urgency.blocked.coefficient=-5.0 # Urgency coefficient for blocked tasks\n" + "urgency.waiting.coefficient=-3.0 # Urgency coefficient for waiting status\n" + "urgency.inherit=0 # Recursively inherit highest urgency value " + "from blocked tasks\n" + "urgency.age.max=365 # Maximum age in days\n" + "\n" + "#urgency.user.project.foo.coefficient=5.0 # Urgency coefficients for 'foo' project\n" + "#urgency.user.tag.foo.coefficient=5.0 # Urgency coefficients for 'foo' tag\n" + "#urgency.uda.foo.coefficient=5.0 # Urgency coefficients for UDA 'foo'\n" + "\n" + "# Color controls.\n" + "color=1 # Enable color\n" + "\n" + "# Here is the rule precedence order, highest to lowest.\n" + "# Note that these are just the color rule names, without the leading 'color.'\n" + "# and any trailing '.value'.\n" + "rule.precedence.color=deleted,completed,active,keyword.,tag.,project.,overdue,scheduled,due." + "today,due,blocked,blocking,recurring,tagged,uda.\n" + "\n" + "# General decoration\n" + "rule.color.merge=1\n" + "color.label=\n" + "color.label.sort=\n" + "color.alternate=on gray2\n" + "color.header=color3\n" + "color.footnote=color3\n" + "color.warning=bold red\n" + "color.error=white on red\n" + "color.debug=color4\n" + "\n" + "# Task state\n" + "color.completed=\n" + "color.deleted=\n" + "color.active=rgb555 on rgb410\n" + "color.recurring=rgb013\n" + "color.scheduled=on rgb001\n" + "color.until=\n" + "color.blocked=white on color8\n" + "color.blocking=black on color15\n" + "\n" + "# Project\n" + "color.project.none=\n" + "\n" + "# Priority UDA\n" + "color.uda.priority.H=color255\n" + "color.uda.priority.L=color245\n" + "color.uda.priority.M=color250\n" + "\n" + "# Tags\n" + "color.tag.next=rgb440\n" + "color.tag.none=\n" + "color.tagged=rgb031\n" + "\n" + "# Due\n" + "color.due.today=rgb400\n" + "color.due=color1\n" + "color.overdue=color9\n" + "\n" + "# Report: burndown\n" + "color.burndown.done=on rgb010\n" + "color.burndown.pending=on color9\n" + "color.burndown.started=on color11\n" + "\n" + "# Report: history\n" + "color.history.add=color0 on rgb500\n" + "color.history.delete=color0 on rgb550\n" + "color.history.done=color0 on rgb050\n" + "\n" + "# Report: summary\n" + "color.summary.background=white on color0\n" + "color.summary.bar=black on rgb141\n" + "\n" + "# Command: calendar\n" + "color.calendar.due.today=color15 on color1\n" + "color.calendar.due=color0 on color1\n" + "color.calendar.holiday=color0 on color11\n" + "color.calendar.scheduled=rgb013 on color15\n" + "color.calendar.overdue=color0 on color9\n" + "color.calendar.today=color15 on rgb013\n" + "color.calendar.weekend=on color235\n" + "color.calendar.weeknumber=rgb013\n" + "\n" + "# Command: sync\n" + "color.sync.added=rgb010\n" + "color.sync.changed=color11\n" + "color.sync.rejected=color9\n" + "\n" + "# Command: undo\n" + "color.undo.after=color2\n" + "color.undo.before=color1\n" + "\n" + "# UDA priority\n" + "uda.priority.type=string # UDA priority is a string type\n" + "uda.priority.label=Priority # UDA priority has a display label'\n" + "uda.priority.values=H,M,L, # UDA priority values are 'H', 'M', 'L' or ''\n" + " # UDA priority sorting is 'H' > 'M' > 'L' > '' " + "(highest to lowest)\n" + "#uda.priority.default=M # UDA priority default value of 'M'\n" + "urgency.uda.priority.H.coefficient=6.0 # UDA priority coefficient for value 'H'\n" + "urgency.uda.priority.M.coefficient=3.9 # UDA priority coefficient for value 'M'\n" + "urgency.uda.priority.L.coefficient=1.8 # UDA priority coefficient for value 'L'\n" + "\n" + "#default.project=foo # Default project for 'add' command\n" + "#default.due=eom # Default due date for 'add' command\n" + "#default.scheduled=eom # Default scheduled date for 'add' command\n" + "default.command=next # When no arguments are specified\n" + "default.timesheet.filter=( +PENDING and start.after:now-4wks ) or ( +COMPLETED and " + "end.after:now-4wks )\n" + "\n" + "_forcecolor=0 # Forces color to be on, even for non TTY " + "output\n" + "complete.all.tags=0 # Include old tag names in '_ags' command\n" + "list.all.projects=0 # Include old project names in 'projects' " + "command\n" + "summary.all.projects=0 # Include old project names in 'summary' " + "command\n" + "list.all.tags=0 # Include old tag names in 'tags' command\n" + "print.empty.columns=0 # Print columns which have no data for any " + "task\n" + "debug=0 # Display diagnostics\n" + "sugar=1 # Syntactic sugar\n" + "obfuscate=0 # Obfuscate data for error reporting\n" + "fontunderline=1 # Uses underlines rather than -------\n" + "\n" + "# WARNING: Please read the documentation (man task-sync) before setting up\n" + "# Taskwarrior for Taskserver synchronization.\n" + "\n" + "#sync.encryption_secret # Encryption secret for sync to a server\n" + "#sync.server.client_id # Client ID for sync to a server\n" + "#sync.server.url # URL of the sync server\n" + "#sync.local.server_dir # Directory for local sync\n" + "#sync.aws.region # region for AWS sync\n" + "#sync.aws.bucket # bucket for AWS sync\n" + "#sync.aws.access_key_id # access_key_id for AWS sync\n" + "#sync.aws.secret_access_key # secret_access_key for AWS sync\n" + "#sync.aws.profile # profile name for AWS sync\n" + "#sync.aws.default_credentials # use default credentials for AWS sync\n" + "#sync.gcp.credential_path # Path to JSON file containing credentials to " + "authenticate GCP Sync\n" + "#sync.gcp.bucket # Bucket for sync to GCP\n" + "\n" + "# Aliases - alternate names for commands\n" + "alias.rm=delete # Alias for the delete command\n" + "alias.history=history.monthly # Prefer monthly over annual history reports\n" + "alias.ghistory=ghistory.monthly # Prefer monthly graphical over annual history " + "reports\n" + "alias.burndown=burndown.weekly # Prefer the weekly burndown chart\n" + "\n" + "# Reports\n" + "\n" + "report.long.description=All details of tasks\n" + "report.long.labels=ID,A,Created,Mod,Deps,P,Project,Tags,Recur,Wait,Sched,Due,Until," + "Description\n" + "report.long.columns=id,start.active,entry,modified.age,depends,priority,project,tags,recur," + "wait.remaining,scheduled,due,until,description\n" + "report.long.filter=status:pending -WAITING\n" + "report.long.sort=modified-\n" + "report.long.context=1\n" + "\n" + "report.list.description=Most details of tasks\n" + "report.list.labels=ID,Active,Age,D,P,Project,Tags,R,Sch,Due,Until,Description,Urg\n" + "report.list.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur." + "indicator,scheduled.countdown,due,until.remaining,description.count,urgency\n" + "report.list.filter=status:pending -WAITING\n" + "report.list.sort=start-,due+,project+,urgency-\n" + "report.list.context=1\n" + "\n" + "report.ls.description=Few details of tasks\n" + "report.ls.labels=ID,A,D,Project,Tags,R,Wait,S,Due,Until,Description\n" + "report.ls.columns=id,start.active,depends.indicator,project,tags,recur.indicator,wait." + "remaining,scheduled.countdown,due.countdown,until.countdown,description.count\n" + "report.ls.filter=status:pending -WAITING\n" + "report.ls.sort=start-,description+\n" + "report.ls.context=1\n" + "\n" + "report.minimal.description=Minimal details of tasks\n" + "report.minimal.labels=ID,Project,Tags,Description\n" + "report.minimal.columns=id,project,tags.count,description.count\n" + "report.minimal.filter=status:pending -WAITING\n" + "report.minimal.sort=project+/,description+\n" + "report.minimal.context=1\n" + "\n" + "report.newest.description=Newest tasks\n" + "report.newest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until," + "Description\n" + "report.newest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority," + "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n" + "report.newest.filter=status:pending -WAITING\n" + "report.newest.sort=entry-\n" + "report.newest.context=1\n" + "\n" + "report.oldest.description=Oldest tasks\n" + "report.oldest.labels=ID,Active,Created,Age,Mod,D,P,Project,Tags,R,Wait,Sch,Due,Until," + "Description\n" + "report.oldest.columns=id,start.age,entry,entry.age,modified.age,depends.indicator,priority," + "project,tags,recur.indicator,wait.remaining,scheduled.countdown,due,until.age,description\n" + "report.oldest.filter=status:pending -WAITING\n" + "report.oldest.sort=entry+\n" + "report.oldest.context=1\n" + "\n" + "report.overdue.description=Overdue tasks\n" + "report.overdue.labels=ID,Active,Age,Deps,P,Project,Tag,R,S,Due,Until,Description,Urg\n" + "report.overdue.columns=id,start.age,entry.age,depends,priority,project,tags,recur.indicator," + "scheduled.countdown,due,until,description,urgency\n" + "report.overdue.filter=status:pending -WAITING +OVERDUE\n" + "report.overdue.sort=urgency-,due+\n" + "report.overdue.context=1\n" + "\n" + "report.active.description=Active tasks\n" + "report.active.labels=ID,Started,Active,Age,D,P,Project,Tags,Recur,W,Sch,Due,Until," + "Description\n" + "report.active.columns=id,start,start.age,entry.age,depends.indicator,priority,project,tags," + "recur,wait,scheduled.remaining,due,until,description\n" + "report.active.filter=status:pending -WAITING +ACTIVE\n" + "report.active.sort=project+,start+\n" + "report.active.context=1\n" + "\n" + "report.completed.description=Completed tasks\n" + "report.completed.labels=ID,UUID,Created,Completed,Age,Deps,P,Project,Tags,R,Due,Description\n" + "report.completed.columns=id,uuid.short,entry,end,entry.age,depends,priority,project,tags," + "recur.indicator,due,description\n" + "report.completed.filter=status:completed -WAITING \n" + "report.completed.sort=end+\n" + "report.completed.context=1\n" + "\n" + "report.recurring.description=Recurring Tasks\n" + "report.recurring.labels=ID,Active,Age,D,P,Parent,Project,Tags,Recur,Sch,Due,Until,Description," + "Urg\n" + "report.recurring.columns=id,start.age,entry.age,depends.indicator,priority,parent.short," + "project,tags,recur,scheduled.countdown,due,until.remaining,description,urgency\n" + "report.recurring.filter=(status:pending -WAITING +CHILD) or (status:recurring -WAITING " + "+PARENT)\n" + "report.recurring.sort=due+,urgency-,entry+\n" + "report.recurring.context=1\n" + "\n" + "report.waiting.description=Waiting (hidden) tasks\n" + "report.waiting.labels=ID,A,Age,D,P,Project,Tags,R,Wait,Remaining,Sched,Due,Until,Description\n" + "report.waiting.columns=id,start.active,entry.age,depends.indicator,priority,project,tags," + "recur.indicator,wait,wait.remaining,scheduled,due,until,description\n" + "report.waiting.filter=+WAITING\n" + "report.waiting.sort=due+,wait+,entry+\n" + "report.waiting.context=1\n" + "\n" + "report.all.description=All tasks\n" + "report.all.labels=ID,St,UUID,A,Age,Done,D,P,Project,Tags,R,Wait,Sch,Due,Until,Description\n" + "report.all.columns=id,status.short,uuid.short,start.active,entry.age,end.age,depends." + "indicator,priority,project.parent,tags.count,recur.indicator,wait.remaining,scheduled." + "remaining,due,until.remaining,description\n" + "report.all.sort=entry-\n" + "report.all.context=1\n" + "\n" + "report.next.description=Most urgent tasks\n" + "report.next.labels=ID,Active,Age,Deps,P,Project,Tag,Recur,S,Due,Until,Description,Urg\n" + "report.next.columns=id,start.age,entry.age,depends,priority,project,tags,recur,scheduled." + "countdown,due.relative,until.remaining,description,urgency\n" + "report.next.filter=status:pending -WAITING limit:page\n" + "report.next.sort=urgency-\n" + "report.next.context=1\n" + "\n" + "report.ready.description=Most urgent actionable tasks\n" + "report.ready.labels=ID,Active,Age,D,P,Project,Tags,R,S,Due,Until,Description,Urg\n" + "report.ready.columns=id,start.age,entry.age,depends.indicator,priority,project,tags,recur." + "indicator,scheduled.countdown,due.countdown,until.remaining,description,urgency\n" + "report.ready.filter=+READY\n" + "report.ready.sort=start-,urgency-\n" + "report.ready.context=1\n" + "\n" + "report.blocked.description=Blocked tasks\n" + "report.blocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n" + "report.blocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n" + "report.blocked.sort=due+,priority-,start-,project+\n" + "report.blocked.filter=status:pending -WAITING +BLOCKED\n" + "report.blocked.context=1\n" + "\n" + "report.unblocked.description=Unblocked tasks\n" + "report.unblocked.columns=id,depends,project,priority,due,start.active,entry.age,description\n" + "report.unblocked.labels=ID,Deps,Proj,Pri,Due,Active,Age,Description\n" + "report.unblocked.sort=due+,priority-,start-,project+\n" + "report.unblocked.filter=status:pending -WAITING -BLOCKED\n" + "report.unblocked.context=1\n" + "\n" + "report.blocking.description=Blocking tasks\n" + "report.blocking.labels=ID,UUID,A,Deps,Project,Tags,R,W,Sch,Due,Until,Description,Urg\n" + "report.blocking.columns=id,uuid.short,start.active,depends,project,tags,recur,wait,scheduled." + "remaining,due.relative,until.remaining,description.count,urgency\n" + "report.blocking.sort=urgency-,due+,entry+\n" + "report.blocking.filter=status:pending -WAITING +BLOCKING\n" + "report.blocking.context=1\n" + "\n" + "report.timesheet.filter=(+PENDING -WAITING start.after:now-4wks) or (+COMPLETED -WAITING " + "end.after:now-4wks)\n" + "report.timesheet.context=0\n" + "\n"; // Supported modifiers, synonyms on the same line. -static const char* modifierNames[] = -{ - "before", "under", "below", - "after", "over", "above", - "by", - "none", - "any", - "is", "equals", - "isnt", "not", - "has", "contains", - "hasnt", - "startswith", "left", - "endswith", "right", - "word", - "noword" -}; +static const char* modifierNames[] = { + "before", "under", "below", "after", "over", "above", "by", "none", + "any", "is", "equals", "isnt", "not", "has", "contains", "hasnt", + "startswith", "left", "endswith", "right", "word", "noword"}; Context* Context::context; //////////////////////////////////////////////////////////////////////////////// -Context& Context::getContext () -{ +Context& Context::getContext() { + assert(Context::context); return *Context::context; } //////////////////////////////////////////////////////////////////////////////// -void Context::setContext (Context* context) -{ - Context::context = context; +void Context::setContext(Context* context) { Context::context = context; } + +//////////////////////////////////////////////////////////////////////////////// +Context::~Context() { + for (auto& com : commands) delete com.second; + + for (auto& col : columns) delete col.second; } //////////////////////////////////////////////////////////////////////////////// -Context::~Context () -{ - for (auto& com : commands) - delete com.second; - - for (auto& col : columns) - delete col.second; -} - -//////////////////////////////////////////////////////////////////////////////// -int Context::initialize (int argc, const char** argv) -{ - timer_total.start (); +int Context::initialize(int argc, const char** argv) { + timer_total.start(); int rc = 0; - home_dir = getenv ("HOME"); + home_dir = getenv("HOME"); - std::vector searchPaths { TASK_RCDIR }; + std::vector searchPaths{TASK_RCDIR}; - try - { + try { //////////////////////////////////////////////////////////////////////////// // // [1] Load the correct config file. @@ -485,51 +528,46 @@ int Context::initialize (int argc, const char** argv) bool taskrc_overridden = false; // XDG_CONFIG_HOME doesn't count as an override (no warning header) - if (! rc_file.exists ()) - { + if (!rc_file.exists()) { // Use XDG_CONFIG_HOME if defined, otherwise default to ~/.config std::string xdg_config_home; - const char* env_xdg_config_home = getenv ("XDG_CONFIG_HOME"); + const char* env_xdg_config_home = getenv("XDG_CONFIG_HOME"); if (env_xdg_config_home) - xdg_config_home = format ("{1}", env_xdg_config_home); + xdg_config_home = format("{1}", env_xdg_config_home); else - xdg_config_home = format ("{1}/.config", home_dir); + xdg_config_home = format("{1}/.config", home_dir); // Ensure the path does not end with '/' - if (xdg_config_home.back () == '/') - xdg_config_home.pop_back(); + if (xdg_config_home.back() == '/') xdg_config_home.pop_back(); // https://github.com/GothenburgBitFactory/libshared/issues/32 - std::string rcfile_path = format ("{1}/task/taskrc", xdg_config_home); + std::string rcfile_path = format("{1}/task/taskrc", xdg_config_home); - File maybe_rc_file = File (rcfile_path); - if ( maybe_rc_file.exists ()) - rc_file = maybe_rc_file; + File maybe_rc_file = File(rcfile_path); + if (maybe_rc_file.exists()) rc_file = maybe_rc_file; } - char *override = getenv ("TASKRC"); - if (override) - { - rc_file = File (override); + char* override = getenv("TASKRC"); + if (override) { + rc_file = File(override); taskrc_overridden = true; } - taskrc_overridden = - CLI2::getOverride (argc, argv, rc_file) || taskrc_overridden; + taskrc_overridden = CLI2::getOverride(argc, argv, rc_file) || taskrc_overridden; // Artificial scope for timing purposes. { Timer timer; - config.parse (configurationDefaults, 1, searchPaths); - config.load (rc_file._data, 1, searchPaths); - debugTiming (format ("Config::load ({1})", rc_file._data), timer); + config.parse(configurationDefaults, 1, searchPaths); + config.load(rc_file._data, 1, searchPaths); + debugTiming(format("Config::load ({1})", rc_file._data), timer); } - CLI2::applyOverrides (argc, argv); + CLI2::applyOverrides(argc, argv); - if (taskrc_overridden && verbose ("override")) - header (format ("TASKRC override: {1}", rc_file._data)); + if (taskrc_overridden && verbose("override")) + header(format("TASKRC override: {1}", rc_file._data)); //////////////////////////////////////////////////////////////////////////// // @@ -544,24 +582,19 @@ int Context::initialize (int argc, const char** argv) bool taskdata_overridden = false; - override = getenv ("TASKDATA"); - if (override) - { - data_dir = Directory (override); - config.set ("data.location", data_dir._data); + override = getenv("TASKDATA"); + if (override) { + data_dir = Directory(override); + config.set("data.location", data_dir._data); taskdata_overridden = true; } - taskdata_overridden = - CLI2::getDataLocation (argc, argv, data_dir) || taskdata_overridden; + taskdata_overridden = CLI2::getDataLocation(argc, argv, data_dir) || taskdata_overridden; - if (taskdata_overridden && verbose ("override")) - header (format ("TASKDATA override: {1}", data_dir._data)); + if (taskdata_overridden && verbose("override")) + header(format("TASKDATA override: {1}", data_dir._data)); - createDefaultConfig (); - - bool create_if_missing = !config.getBoolean ("exit.on.missing.db"); - tdb2.open_replica (data_dir, create_if_missing); + createDefaultConfig(); //////////////////////////////////////////////////////////////////////////// // @@ -569,9 +602,8 @@ int Context::initialize (int argc, const char** argv) // //////////////////////////////////////////////////////////////////////////// - Command::factory (commands); - for (auto& cmd : commands) - cli2.entity ("cmd", cmd.first); + Command::factory(commands); + for (auto& cmd : commands) cli2.entity("cmd", cmd.first); //////////////////////////////////////////////////////////////////////////// // @@ -579,11 +611,10 @@ int Context::initialize (int argc, const char** argv) // //////////////////////////////////////////////////////////////////////////// - Column::factory (columns); - for (auto& col : columns) - cli2.entity ("attribute", col.first); + Column::factory(columns); + for (auto& col : columns) cli2.entity("attribute", col.first); - cli2.entity ("pseudo", "limit"); + cli2.entity("pseudo", "limit"); //////////////////////////////////////////////////////////////////////////// // @@ -591,14 +622,11 @@ int Context::initialize (int argc, const char** argv) // //////////////////////////////////////////////////////////////////////////// - for (auto& modifierName : modifierNames) - cli2.entity ("modifier", modifierName); + for (auto& modifierName : modifierNames) cli2.entity("modifier", modifierName); - for (auto& op : Eval::getOperators ()) - cli2.entity ("operator", op); + for (auto& op : Eval::getOperators()) cli2.entity("operator", op); - for (auto& op : Eval::getBinaryOperators ()) - cli2.entity ("binary_operator", op); + for (auto& op : Eval::getBinaryOperators()) cli2.entity("binary_operator", op); //////////////////////////////////////////////////////////////////////////// // @@ -606,10 +634,10 @@ int Context::initialize (int argc, const char** argv) // //////////////////////////////////////////////////////////////////////////// - initializeColorRules (); - staticInitialization (); - propagateDebug (); - loadAliases (); + initializeColorRules(); + staticInitialization(); + propagateDebug(); + loadAliases(); //////////////////////////////////////////////////////////////////////////// // @@ -617,97 +645,101 @@ int Context::initialize (int argc, const char** argv) // //////////////////////////////////////////////////////////////////////////// - for (int i = 0; i < argc; i++) - cli2.add (argv[i]); + for (int i = 0; i < argc; i++) cli2.add(argv[i]); - cli2.analyze (); + cli2.analyze(); // Extract a recomposed command line. auto foundDefault = false; auto foundAssumed = false; std::string combined; - for (auto& a : cli2._args) - { - if (combined.length ()) - combined += ' '; + for (auto& a : cli2._args) { + if (combined.length()) combined += ' '; - combined += a.attribute ("raw"); + combined += a.attribute("raw"); - if (a.hasTag ("DEFAULT")) - foundDefault = true; + if (a.hasTag("DEFAULT")) foundDefault = true; - if (a.hasTag ("ASSUMED")) - foundAssumed = true; + if (a.hasTag("ASSUMED")) foundAssumed = true; } - if (verbose ("default")) { - if (foundDefault) - header ("[" + combined + "]"); + if (verbose("default")) { + if (foundDefault) header("[" + combined + "]"); - if (foundAssumed) - header ("No command specified - assuming 'information'."); + if (foundAssumed) header("No command specified - assuming 'information'."); } + //////////////////////////////////////////////////////////////////////////// + // + // [7.5] Open the Replica. + // + //////////////////////////////////////////////////////////////////////////// + + bool create_if_missing = !config.getBoolean("exit.on.missing.db"); + Command* c = commands[cli2.getCommand()]; + + // We must allow writes if either 'gc' is enabled and the command performs GC, or the command + // itself is read-write. + bool read_write = + (config.getBoolean("gc") && (c->needs_gc() || c->needs_recur_update())) || !c->read_only(); + tdb2.open_replica(data_dir, create_if_missing, read_write); + //////////////////////////////////////////////////////////////////////////// // // [8] Initialize hooks. // //////////////////////////////////////////////////////////////////////////// - hooks.initialize (); + hooks.initialize(); } - catch (const std::string& message) - { - error (message); + catch (const std::string& message) { + error(message); rc = 2; } - catch (int) - { + catch (rust::Error& err) { + error(err.what()); + rc = 2; + } + + catch (int) { // Hooks can terminate processing by throwing integers. rc = 4; } - catch (const std::regex_error& e) - { + catch (const std::regex_error& e) { std::cout << "regex_error caught: " << e.what() << '\n'; - } - catch (...) - { - error ("knknown error. Please report."); + } catch (...) { + error("Unknown error. Please report."); rc = 3; } // On initialization failure... - if (rc) - { + if (rc) { // Dump all debug messages, controlled by rc.debug. - if (config.getBoolean ("debug")) - { + if (config.getBoolean("debug")) { for (auto& d : debugMessages) - if (color ()) - std::cerr << colorizeDebug (d) << '\n'; + if (color()) + std::cerr << colorizeDebug(d) << '\n'; else std::cerr << d << '\n'; } // Dump all headers, controlled by 'header' verbosity token. - if (verbose ("header")) - { + if (verbose("header")) { for (auto& h : headers) - if (color ()) - std::cerr << colorizeHeader (h) << '\n'; + if (color()) + std::cerr << colorizeHeader(h) << '\n'; else std::cerr << h << '\n'; } // Dump all footnotes, controlled by 'footnote' verbosity token. - if (verbose ("footnote")) - { + if (verbose("footnote")) { for (auto& f : footnotes) - if (color ()) - std::cerr << colorizeFootnote (f) << '\n'; + if (color()) + std::cerr << colorizeFootnote(f) << '\n'; else std::cerr << f << '\n'; } @@ -715,98 +747,82 @@ int Context::initialize (int argc, const char** argv) // Dump all errors, non-maskable. // Colorized as footnotes. for (auto& e : errors) - if (color ()) - std::cerr << colorizeFootnote (e) << '\n'; + if (color()) + std::cerr << colorizeFootnote(e) << '\n'; else std::cerr << e << '\n'; } - time_init_us += timer_total.total_us (); + time_init_us += timer_total.total_us(); return rc; } //////////////////////////////////////////////////////////////////////////////// -int Context::run () -{ +int Context::run() { int rc; std::string output; - try - { - hooks.onLaunch (); - rc = dispatch (output); - hooks.onExit (); // No chance to update data. + try { + hooks.onLaunch(); + rc = dispatch(output); + hooks.onExit(); // No chance to update data. - timer_total.stop (); - time_total_us += timer_total.total_us (); + timer_total.stop(); + time_total_us += timer_total.total_us(); std::stringstream s; - s << "Perf " - << PACKAGE_STRING - << ' ' + s << "Perf " << PACKAGE_STRING << ' ' #ifdef HAVE_COMMIT << COMMIT #else << '-' #endif - << ' ' - << Datetime ().toISO () + << ' ' << Datetime().toISO() - << " init:" << time_init_us - << " load:" << time_load_us - << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us) - << " filter:" << time_filter_us - << " commit:" << time_commit_us - << " sort:" << time_sort_us - << " render:" << time_render_us - << " hooks:" << time_hooks_us - << " other:" << time_total_us - - time_init_us - - time_gc_us - - time_filter_us - - time_commit_us - - time_sort_us - - time_render_us - - time_hooks_us - << " total:" << time_total_us - << '\n'; - debug (s.str ()); + << " init:" << time_init_us << " load:" << time_load_us + << " gc:" << (time_gc_us > 0 ? time_gc_us - time_load_us : time_gc_us) + << " filter:" << time_filter_us << " commit:" << time_commit_us << " sort:" << time_sort_us + << " render:" << time_render_us << " hooks:" << time_hooks_us << " other:" + << time_total_us - time_init_us - time_gc_us - time_filter_us - time_commit_us - + time_sort_us - time_render_us - time_hooks_us + << " total:" << time_total_us << '\n'; + debug(s.str()); } - catch (const std::string& message) - { - error (message); + catch (const std::string& message) { + error(message); rc = 2; } - catch (int) - { + catch (rust::Error& err) { + error(err.what()); + rc = 2; + } + + catch (int) { // Hooks can terminate processing by throwing integers. rc = 4; } - catch (...) - { - error ("Unknown error. Please report."); + catch (...) { + error("Unknown error. Please report."); rc = 3; } // Dump all debug messages, controlled by rc.debug. - if (config.getBoolean ("debug")) - { + if (config.getBoolean("debug")) { for (auto& d : debugMessages) - if (color ()) - std::cerr << colorizeDebug (d) << '\n'; + if (color()) + std::cerr << colorizeDebug(d) << '\n'; else std::cerr << d << '\n'; } // Dump all headers, controlled by 'header' verbosity token. - if (verbose ("header")) - { + if (verbose("header")) { for (auto& h : headers) - if (color ()) - std::cerr << colorizeHeader (h) << '\n'; + if (color()) + std::cerr << colorizeHeader(h) << '\n'; else std::cerr << h << '\n'; } @@ -815,11 +831,10 @@ int Context::run () std::cout << output; // Dump all footnotes, controlled by 'footnote' verbosity token. - if (verbose ("footnote")) - { + if (verbose("footnote")) { for (auto& f : footnotes) - if (color ()) - std::cerr << colorizeFootnote (f) << '\n'; + if (color()) + std::cerr << colorizeFootnote(f) << '\n'; else std::cerr << f << '\n'; } @@ -827,8 +842,8 @@ int Context::run () // Dump all errors, non-maskable. // Colorized as footnotes. for (auto& e : errors) - if (color ()) - std::cerr << colorizeError (e) << '\n'; + if (color()) + std::cerr << colorizeError(e) << '\n'; else std::cerr << e << '\n'; @@ -837,69 +852,57 @@ int Context::run () //////////////////////////////////////////////////////////////////////////////// // Dispatch to the command found by the CLI parser. -int Context::dispatch (std::string &out) -{ +int Context::dispatch(std::string& out) { // Autocomplete args against keywords. - std::string command = cli2.getCommand (); - if (command != "") - { - updateXtermTitle (); - updateVerbosity (); + std::string command = cli2.getCommand(); + if (command != "") { + updateXtermTitle(); + updateVerbosity(); Command* c = commands[command]; - assert (c); + assert(c); - // The command know whether they need a GC. - if (c->needs_gc ()) - { - run_gc = config.getBoolean ("gc"); - tdb2.gc (); - } - else - { - run_gc = false; + // The command know whether they need a GC or recurrence update. + if (c->needs_gc()) { + tdb2.gc(); } // This is something that is only needed for write commands with no other // filter processing. - if (c->accepts_modifications () && - ! c->accepts_filter ()) - { - cli2.prepareFilter (); + if (c->accepts_modifications() && !c->accepts_filter()) { + cli2.prepareFilter(); } // With rc.debug.parser == 2, there are more tree dumps than you might want, // but we need the rc.debug.parser == 1 case covered also, with the final // tree. - if (config.getBoolean ("debug") && - config.getInteger ("debug.parser") == 1) - debug (cli2.dump ("Parse Tree (before command-specifіc processing)")); + if (config.getBoolean("debug") && config.getInteger("debug.parser") == 1) + debug(cli2.dump("Parse Tree (before command-specifіc processing)")); - return c->execute (out); + if (c->needs_recur_update() && Context::getContext().config.getBoolean("gc")) { + handleUntil(); + handleRecurrence(); + } + + return c->execute(out); } - assert (commands["help"]); - return commands["help"]->execute (out); + assert(commands["help"]); + return commands["help"]->execute(out); } //////////////////////////////////////////////////////////////////////////////// -int Context::getWidth () -{ +int Context::getWidth() { // Determine window size. - auto width = config.getInteger ("defaultwidth"); + auto width = config.getInteger("defaultwidth"); // A zero width value means 'infinity', which is approximated here by 2^16. - if (width == 0) - return 65536; + if (width == 0) return 65536; - if (config.getBoolean ("detection")) - { - if (terminal_width == 0 && - terminal_height == 0) - { + if (config.getBoolean("detection")) { + if (terminal_width == 0 && terminal_height == 0) { unsigned short buff[4]; - if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) - { + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) { terminal_height = buff[0]; terminal_width = buff[1]; } @@ -910,31 +913,24 @@ int Context::getWidth () // Ncurses does this, and perhaps we need to as well, to avoid a problem on // Cygwin where the display goes right up to the terminal width, and causes // an odd color wrapping problem. - if (config.getBoolean ("avoidlastcolumn")) - --width; + if (config.getBoolean("avoidlastcolumn")) --width; } return width; } //////////////////////////////////////////////////////////////////////////////// -int Context::getHeight () -{ +int Context::getHeight() { // Determine window size. - auto height = config.getInteger ("defaultheight"); + auto height = config.getInteger("defaultheight"); // A zero height value means 'infinity', which is approximated here by 2^16. - if (height == 0) - return 65536; + if (height == 0) return 65536; - if (config.getBoolean ("detection")) - { - if (terminal_width == 0 && - terminal_height == 0) - { + if (config.getBoolean("detection")) { + if (terminal_width == 0 && terminal_height == 0) { unsigned short buff[4]; - if (ioctl (STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) - { + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &buff) != -1) { terminal_height = buff[0]; terminal_width = buff[1]; } @@ -947,63 +943,54 @@ int Context::getHeight () } //////////////////////////////////////////////////////////////////////////////// -std::string Context::getTaskContext (const std::string& kind, std::string name, bool fallback /* = true */) -{ +std::string Context::getTaskContext(const std::string& kind, std::string name, + bool fallback /* = true */) { // Consider currently selected context, if none specified - if (name.empty ()) - name = config.get ("context"); + if (name.empty()) name = config.get("context"); // Detect if any context is set, and bail out if not - if (! name.empty ()) - debug (format ("Applying context '{1}'", name)); - else - { - debug ("No context set"); + if (!name.empty()) + debug(format("Applying context '{1}'", name)); + else { + debug("No context set"); return ""; } // Figure out the context string for this kind (read/write) std::string contextString = ""; - if (! config.has ("context." + name + "." + kind) && kind == "read") - { - debug ("Specific " + kind + " context for '" + name + "' not defined. "); - if (fallback) - { - debug ("Trying to interpret old-style context definition as read context."); - contextString = config.get ("context." + name); + if (!config.has("context." + name + "." + kind) && kind == "read") { + debug("Specific " + kind + " context for '" + name + "' not defined. "); + if (fallback) { + debug("Trying to interpret old-style context definition as read context."); + contextString = config.get("context." + name); } - } - else - contextString = config.get ("context." + name + "." + kind); + } else + contextString = config.get("context." + name + "." + kind); - debug (format ("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString)); + debug(format("Detected context string: {1}", contextString.empty() ? "(empty)" : contextString)); return contextString; } //////////////////////////////////////////////////////////////////////////////// -bool Context::color () -{ - if (determine_color_use) - { +bool Context::color() { + if (determine_color_use) { // What the config says. - use_color = config.getBoolean ("color"); + use_color = config.getBoolean("color"); // Only tty's support color. - if (! isatty (STDOUT_FILENO)) - { + if (!isatty(STDOUT_FILENO)) { // No ioctl. - config.set ("detection", "off"); - config.set ("color", "off"); + config.set("detection", "off"); + config.set("color", "off"); // Files don't get color. use_color = false; } // Override. - if (config.getBoolean ("_forcecolor")) - { - config.set ("color", "on"); + if (config.getBoolean("_forcecolor")) { + config.set("color", "on"); use_color = true; } @@ -1026,66 +1013,55 @@ bool Context::color () // TODO This mechanism is clunky, and should slowly evolve into something more // logical and consistent. This should probably mean that 'nothing' should // take the place of '0'. -bool Context::verbose (const std::string& token) -{ - if (verbosity.empty ()) - { - verbosity_legacy = config.getBoolean ("verbose"); - for (auto& token : split (config.get ("verbose"), ',')) - verbosity.insert (token); +bool Context::verbose(const std::string& token) { + if (verbosity.empty()) { + verbosity_legacy = config.getBoolean("verbose"); + for (auto& token : split(config.get("verbose"), ',')) verbosity.insert(token); // Regular feedback means almost everything. // This odd test is to see if a Boolean-false value is a real one, which // means it is not 1/true/T/yes/on, but also should not be one of the // valid tokens either. - if (! verbosity_legacy && ! verbosity.empty ()) - { - std::string v = *(verbosity.begin ()); - if (v != "nothing" && - v != "affected" && // This list must be complete. - v != "blank" && // - v != "context" && // - v != "default" && // - v != "edit" && // - v != "filter" && // - v != "footnote" && // - v != "header" && // - v != "label" && // - v != "new-id" && // - v != "new-uuid" && // - v != "override" && // - v != "project" && // - v != "recur" && // - v != "special" && // - v != "sync") - { + if (!verbosity_legacy && !verbosity.empty()) { + std::string v = *(verbosity.begin()); + if (v != "nothing" && v != "affected" && // This list must be complete. + v != "blank" && // + v != "context" && // + v != "default" && // + v != "edit" && // + v != "filter" && // + v != "footnote" && // + v != "header" && // + v != "label" && // + v != "new-id" && // + v != "new-uuid" && // + v != "news" && // + v != "override" && // + v != "project" && // + v != "recur" && // + v != "special" && // + v != "sync") { // This list emulates rc.verbose=off in version 1.9.4. verbosity = {"blank", "label", "new-id", "edit"}; } } // Some flags imply "footnote" verbosity being active. Make it so. - if (! verbosity.count ("footnote")) - { + if (!verbosity.count("footnote")) { // TODO: Some of these may not use footnotes yet. They should. - for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) - { - if (verbosity.count (flag)) - { - verbosity.insert ("footnote"); + for (auto flag : {"affected", "new-id", "new-uuid", "project", "override", "recur"}) { + if (verbosity.count(flag)) { + verbosity.insert("footnote"); break; } } } // Some flags imply "header" verbosity being active. Make it so. - if (! verbosity.count ("header")) - { - for (auto flag : {"default"}) - { - if (verbosity.count (flag)) - { - verbosity.insert ("header"); + if (!verbosity.count("header")) { + for (auto flag : {"default"}) { + if (verbosity.count(flag)) { + verbosity.insert("header"); break; } } @@ -1093,27 +1069,21 @@ bool Context::verbose (const std::string& token) } // rc.verbose=true|y|yes|1|on overrides all. - if (verbosity_legacy) - return true; + if (verbosity_legacy) return true; // rc.verbose=nothing overrides all. - if (verbosity.size () == 1 && - *(verbosity.begin ()) == "nothing") - return false; + if (verbosity.size() == 1 && *(verbosity.begin()) == "nothing") return false; // Specific token match. - if (verbosity.count (token)) - return true; + if (verbosity.count(token)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -const std::vector Context::getColumns () const -{ - std::vector output; - for (auto& col : columns) - output.push_back (col.first); +const std::vector Context::getColumns() const { + std::vector output; + for (auto& col : columns) output.push_back(col.first); return output; } @@ -1122,23 +1092,18 @@ const std::vector Context::getColumns () const // A value of zero mean unlimited. // A value of 'page' means however many screen lines there are. // A value of a positive integer is a row/task limit. -void Context::getLimits (int& rows, int& lines) -{ +void Context::getLimits(int& rows, int& lines) { rows = 0; lines = 0; // This is an integer specified as a filter (limit:10). - auto limit = config.get ("limit"); - if (limit != "") - { - if (limit == "page") - { + auto limit = config.get("limit"); + if (limit != "") { + if (limit == "page") { rows = 0; - lines = getHeight (); - } - else - { - rows = (int) strtol (limit.c_str (), nullptr, 10); + lines = getHeight(); + } else { + rows = (int)strtol(limit.c_str(), nullptr, 10); lines = 0; } } @@ -1147,94 +1112,94 @@ void Context::getLimits (int& rows, int& lines) //////////////////////////////////////////////////////////////////////////////// // The 'Task' object, among others, is shared between projects. To make this // easier, it has been decoupled from Context. -void Context::staticInitialization () -{ - CLI2::minimumMatchLength = config.getInteger ("abbreviation.minimum"); - Lexer::minimumMatchLength = config.getInteger ("abbreviation.minimum"); +void Context::staticInitialization() { + CLI2::minimumMatchLength = config.getInteger("abbreviation.minimum"); + Lexer::minimumMatchLength = config.getInteger("abbreviation.minimum"); - Task::defaultProject = config.get ("default.project"); - Task::defaultDue = config.get ("default.due"); - Task::defaultScheduled = config.get ("default.scheduled"); + Task::defaultProject = config.get("default.project"); + Task::defaultDue = config.get("default.due"); + Task::defaultScheduled = config.get("default.scheduled"); - Task::searchCaseSensitive = Variant::searchCaseSensitive = config.getBoolean ("search.case.sensitive"); - Task::regex = Variant::searchUsingRegex = config.getBoolean ("regex"); - Lexer::dateFormat = Variant::dateFormat = config.get ("dateformat"); + Task::searchCaseSensitive = Variant::searchCaseSensitive = + config.getBoolean("search.case.sensitive"); + Task::regex = Variant::searchUsingRegex = config.getBoolean("regex"); + Lexer::dateFormat = Variant::dateFormat = config.get("dateformat"); - Datetime::isoEnabled = config.getBoolean ("date.iso"); - Datetime::standaloneDateEnabled = false; - Datetime::standaloneTimeEnabled = false; + auto weekStart = Datetime::dayOfWeek(config.get("weekstart")); + if (weekStart != 0 && weekStart != 1) + throw std::string( + "The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'."); + Datetime::weekstart = weekStart; + Datetime::isoEnabled = config.getBoolean("date.iso"); + Datetime::standaloneDateEnabled = false; + Datetime::standaloneTimeEnabled = false; Duration::standaloneSecondsEnabled = false; - TDB2::debug_mode = config.getBoolean ("debug"); + TDB2::debug_mode = config.getBoolean("debug"); - for (auto& rc : config) - { - if (rc.first.substr (0, 4) == "uda." && - rc.first.substr (rc.first.length () - 7, 7) == ".values") - { - std::string name = rc.first.substr (4, rc.first.length () - 7 - 4); - auto values = split (rc.second, ','); + for (auto& rc : config) { + if (rc.first.substr(0, 4) == "uda." && rc.first.substr(rc.first.length() - 7, 7) == ".values") { + std::string name = rc.first.substr(4, rc.first.length() - 7 - 4); + auto values = split(rc.second, ','); - for (auto r = values.rbegin(); r != values.rend (); ++r) - Task::customOrder[name].push_back (*r); + for (auto r = values.rbegin(); r != values.rend(); ++r) Task::customOrder[name].push_back(*r); } } - for (auto& col : columns) - { - Task::attributes[col.first] = col.second->type (); - Lexer::attributes[col.first] = col.second->type (); + for (auto& col : columns) { + Task::attributes[col.first] = col.second->type(); + Lexer::attributes[col.first] = col.second->type(); } - Task::urgencyProjectCoefficient = config.getReal ("urgency.project.coefficient"); - Task::urgencyActiveCoefficient = config.getReal ("urgency.active.coefficient"); - Task::urgencyScheduledCoefficient = config.getReal ("urgency.scheduled.coefficient"); - Task::urgencyWaitingCoefficient = config.getReal ("urgency.waiting.coefficient"); - Task::urgencyBlockedCoefficient = config.getReal ("urgency.blocked.coefficient"); - Task::urgencyAnnotationsCoefficient = config.getReal ("urgency.annotations.coefficient"); - Task::urgencyTagsCoefficient = config.getReal ("urgency.tags.coefficient"); - Task::urgencyDueCoefficient = config.getReal ("urgency.due.coefficient"); - Task::urgencyBlockingCoefficient = config.getReal ("urgency.blocking.coefficient"); - Task::urgencyAgeCoefficient = config.getReal ("urgency.age.coefficient"); - Task::urgencyAgeMax = config.getReal ("urgency.age.max"); + Task::urgencyProjectCoefficient = config.getReal("urgency.project.coefficient"); + Task::urgencyActiveCoefficient = config.getReal("urgency.active.coefficient"); + Task::urgencyScheduledCoefficient = config.getReal("urgency.scheduled.coefficient"); + Task::urgencyWaitingCoefficient = config.getReal("urgency.waiting.coefficient"); + Task::urgencyBlockedCoefficient = config.getReal("urgency.blocked.coefficient"); + Task::urgencyAnnotationsCoefficient = config.getReal("urgency.annotations.coefficient"); + Task::urgencyTagsCoefficient = config.getReal("urgency.tags.coefficient"); + Task::urgencyDueCoefficient = config.getReal("urgency.due.coefficient"); + Task::urgencyBlockingCoefficient = config.getReal("urgency.blocking.coefficient"); + Task::urgencyAgeCoefficient = config.getReal("urgency.age.coefficient"); + Task::urgencyAgeMax = config.getReal("urgency.age.max"); // Tag- and project-specific coefficients. - for (auto& var : config.all ()) - if (var.substr (0, 13) == "urgency.user." || - var.substr (0, 12) == "urgency.uda.") - Task::coefficients[var] = config.getReal (var); + for (auto& var : config.all()) + if (var.substr(0, 13) == "urgency.user." || var.substr(0, 12) == "urgency.uda.") + Task::coefficients[var] = config.getReal(var); } //////////////////////////////////////////////////////////////////////////////// -void Context::createDefaultConfig () -{ +void Context::createDefaultConfig() { // Do we need to create a default rc? - if (rc_file._data != "" && ! rc_file.exists ()) - { - if (config.getBoolean ("confirmation") && - ! confirm ( format ("A configuration file could not be found in {1}\n\nWould you like a sample {2} created, so Taskwarrior can proceed?", home_dir, rc_file._data))) - throw std::string ("Cannot proceed without rc file."); + if (rc_file._data != "" && !rc_file.exists()) { + // If stdout is not a file, we are probably executing in a completion context and should not + // prompt (as the user won't see it) or modify the config (as completion functions are typically + // read-only). + if (!isatty(STDOUT_FILENO)) { + throw std::string("Cannot proceed without rc file."); + } - // Override data.location in the defaults. - auto loc = configurationDefaults.find ("data.location=~/.task"); - // loc+0^ +14^ +21^ + if (config.getBoolean("confirmation") && + !confirm(format("A configuration file could not be found in {1}\n\nWould you like a sample " + "{2} created, so Taskwarrior can proceed?", + home_dir, rc_file._data))) + throw std::string("Cannot proceed without rc file."); Datetime now; std::stringstream contents; - contents << "# [Created by " - << PACKAGE_STRING - << ' ' - << now.toString ("m/d/Y H:N:S") - << "]\n" - << configurationDefaults.substr (0, loc + 14) - << data_dir._original - << "\n\n# To use the default location of the XDG directories,\n" - << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and uncomment below\n" + contents << "# [Created by " << PACKAGE_STRING << ' ' << now.toString("m/d/Y H:N:S") << "]\n" + << "data.location=" << data_dir._original << "\n" + << "news.version=" << Version::Current() << "\n" + << "\n# To use the default location of the XDG directories,\n" + << "# move this configuration file from ~/.taskrc to ~/.config/task/taskrc and update " + "location config as follows:\n" << "\n#data.location=~/.local/share/task\n" << "#hooks.location=~/.config/task/hooks\n" << "\n# Color theme (uncomment one to use)\n" << "#include light-16.theme\n" << "#include light-256.theme\n" + << "#include bubblegum-256.theme\n" << "#include dark-16.theme\n" << "#include dark-256.theme\n" << "#include dark-red-256.theme\n" @@ -1250,81 +1215,61 @@ void Context::createDefaultConfig () << '\n'; // Write out the new file. - if (! File::write (rc_file._data, contents.str ())) - throw format ("Could not write to '{1}'.", rc_file._data); + if (!File::write(rc_file._data, contents.str())) + throw format("Could not write to '{1}'.", rc_file._data); + + // Load it so that it takes effect for this run. + config.load(rc_file); } } //////////////////////////////////////////////////////////////////////////////// -void Context::decomposeSortField ( - const std::string& field, - std::string& key, - bool& ascending, - bool& breakIndicator) -{ - int length = field.length (); +void Context::decomposeSortField(const std::string& field, std::string& key, bool& ascending, + bool& breakIndicator) { + int length = field.length(); int decoration = 1; breakIndicator = false; - if (field[length - decoration] == '/') - { + if (field[length - decoration] == '/') { breakIndicator = true; ++decoration; } - if (field[length - decoration] == '+') - { + if (field[length - decoration] == '+') { ascending = true; - key = field.substr (0, length - decoration); - } - else if (field[length - decoration] == '-') - { + key = field.substr(0, length - decoration); + } else if (field[length - decoration] == '-') { ascending = false; - key = field.substr (0, length - decoration); - } - else - { + key = field.substr(0, length - decoration); + } else { ascending = true; key = field; } } //////////////////////////////////////////////////////////////////////////////// -void Context::debugTiming (const std::string& details, const Timer& timer) -{ +void Context::debugTiming(const std::string& details, const Timer& timer) { std::stringstream out; - out << "Timer " - << details - << ' ' - << std::setprecision (6) - << std::fixed - << timer.total_us () / 1.0e6 - << " sec"; - debug (out.str ()); + out << "Timer " << details << ' ' << std::setprecision(6) << std::fixed + << timer.total_us() / 1.0e6 << " sec"; + debug(out.str()); } //////////////////////////////////////////////////////////////////////////////// -CurrentTask Context::withCurrentTask (const Task *task) -{ - return CurrentTask(*this, task); -} +CurrentTask Context::withCurrentTask(const Task* task) { return CurrentTask(*this, task); } //////////////////////////////////////////////////////////////////////////////// // This capability is to answer the question of 'what did I just do to generate // this output?'. -void Context::updateXtermTitle () -{ - if (config.getBoolean ("xterm.title") && isatty (STDOUT_FILENO)) - { - auto command = cli2.getCommand (); +void Context::updateXtermTitle() { + if (config.getBoolean("xterm.title") && isatty(STDOUT_FILENO)) { + auto command = cli2.getCommand(); std::string title; - for (auto a = cli2._args.begin (); a != cli2._args.end (); ++a) - { - if (a != cli2._args.begin ()) - title += ' '; + for (auto a = cli2._args.begin(); a != cli2._args.end(); ++a) { + if (a != cli2._args.begin()) title += ' '; - title += a->attribute ("raw"); + title += a->attribute("raw"); } std::cout << "]0;task " << command << ' ' << title << ""; @@ -1333,92 +1278,69 @@ void Context::updateXtermTitle () //////////////////////////////////////////////////////////////////////////////// // This function allows a clean output if the command is a helper subcommand. -void Context::updateVerbosity () -{ - auto command = cli2.getCommand (); - if (command != "" && - command[0] == '_') - { +void Context::updateVerbosity() { + auto command = cli2.getCommand(); + if (command != "" && command[0] == '_') { verbosity = {"nothing"}; } } //////////////////////////////////////////////////////////////////////////////// -void Context::loadAliases () -{ +void Context::loadAliases() { for (auto& i : config) - if (i.first.substr (0, 6) == "alias.") - cli2.alias (i.first.substr (6), i.second); + if (i.first.substr(0, 6) == "alias.") cli2.alias(i.first.substr(6), i.second); } //////////////////////////////////////////////////////////////////////////////// // Using the general rc.debug setting automaticalls sets debug.hooks // and debug.parser, unless they already have values, which by default they do // not. -void Context::propagateDebug () -{ - if (config.getBoolean ("debug")) - { - if (! config.has ("debug.hooks")) - config.set ("debug.hooks", 1); +void Context::propagateDebug() { + if (config.getBoolean("debug")) { + if (!config.has("debug.hooks")) config.set("debug.hooks", 1); - if (! config.has ("debug.parser")) - config.set ("debug.parser", 1); - } - else - { - if ((config.has ("debug.hooks") && config.getInteger ("debug.hooks")) || - (config.has ("debug.parser") && config.getInteger ("debug.parser")) ) - config.set ("debug", true); + if (!config.has("debug.parser")) config.set("debug.parser", 1); + } else { + if ((config.has("debug.hooks") && config.getInteger("debug.hooks")) || + (config.has("debug.parser") && config.getInteger("debug.parser"))) + config.set("debug", true); } } //////////////////////////////////////////////////////////////////////////////// // No duplicates. -void Context::header (const std::string& input) -{ - if (input.length () && - std::find (headers.begin (), headers.end (), input) == headers.end ()) - headers.push_back (input); +void Context::header(const std::string& input) { + if (input.length() && std::find(headers.begin(), headers.end(), input) == headers.end()) + headers.push_back(input); } //////////////////////////////////////////////////////////////////////////////// // No duplicates. -void Context::footnote (const std::string& input) -{ - if (input.length () && - std::find (footnotes.begin (), footnotes.end (), input) == footnotes.end ()) - footnotes.push_back (input); +void Context::footnote(const std::string& input) { + if (input.length() && std::find(footnotes.begin(), footnotes.end(), input) == footnotes.end()) + footnotes.push_back(input); } //////////////////////////////////////////////////////////////////////////////// // No duplicates. -void Context::error (const std::string& input) -{ - if (input.length () && - std::find (errors.begin (), errors.end (), input) == errors.end ()) - errors.push_back (input); +void Context::error(const std::string& input) { + if (input.length() && std::find(errors.begin(), errors.end(), input) == errors.end()) + errors.push_back(input); } //////////////////////////////////////////////////////////////////////////////// -void Context::debug (const std::string& input) -{ - if (input.length ()) - debugMessages.push_back (input); +void Context::debug(const std::string& input) { + if (input.length()) debugMessages.push_back(input); } //////////////////////////////////////////////////////////////////////////////// -CurrentTask::CurrentTask (Context &context, const Task *task) - : context {context}, previous {context.currentTask} -{ +CurrentTask::CurrentTask(Context& context, const Task* task) + : context{context}, previous{context.currentTask} { context.currentTask = task; } //////////////////////////////////////////////////////////////////////////////// -CurrentTask::~CurrentTask () -{ - context.currentTask = previous; -} +CurrentTask::~CurrentTask() { context.currentTask = previous; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Context.h b/src/Context.h index 310613eef..a456364e9 100644 --- a/src/Context.h +++ b/src/Context.h @@ -27,112 +27,111 @@ #ifndef INCLUDED_CONTEXT #define INCLUDED_CONTEXT -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include #include + #include class CurrentTask; -class Context -{ -public: - Context () = default; // Default constructor - ~Context (); // Destructor +class Context { + public: + Context() = default; // Default constructor + ~Context(); // Destructor - Context (const Context&); - Context& operator= (const Context&); + Context(const Context &); + Context &operator=(const Context &); - static Context& getContext (); - static void setContext (Context*); + static Context &getContext(); + static void setContext(Context *); - int initialize (int, const char**); // all startup - int run (); - int dispatch (std::string&); // command handler dispatch + int initialize(int, const char **); // all startup + int run(); + int dispatch(std::string &); // command handler dispatch - int getWidth (); // determine terminal width - int getHeight (); // determine terminal height + int getWidth(); // determine terminal width + int getHeight(); // determine terminal height - std::string getTaskContext (const std::string&, std::string, bool fallback=true); + std::string getTaskContext(const std::string &, std::string, bool fallback = true); - const std::vector getColumns () const; - void getLimits (int&, int&); + const std::vector getColumns() const; + void getLimits(int &, int &); - bool color (); // TTY or ? - bool verbose (const std::string&); // Verbosity control + bool color(); // TTY or ? + bool verbose(const std::string &); // Verbosity control - void header (const std::string&); // Header message sink - void footnote (const std::string&); // Footnote message sink - void debug (const std::string&); // Debug message sink - void error (const std::string&); // Error message sink - non-maskable + void header(const std::string &); // Header message sink + void footnote(const std::string &); // Footnote message sink + void debug(const std::string &); // Debug message sink + void error(const std::string &); // Error message sink - non-maskable - void decomposeSortField (const std::string&, std::string&, bool&, bool&); - void debugTiming (const std::string&, const Timer&); + void decomposeSortField(const std::string &, std::string &, bool &, bool &); + void debugTiming(const std::string &, const Timer &); - CurrentTask withCurrentTask (const Task *); + CurrentTask withCurrentTask(const Task *); friend class CurrentTask; -private: - void staticInitialization (); - void createDefaultConfig (); - void updateXtermTitle (); - void updateVerbosity (); - void loadAliases (); - void propagateDebug (); + private: + void staticInitialization(); + void createDefaultConfig(); + void updateXtermTitle(); + void updateVerbosity(); + void loadAliases(); + void propagateDebug(); - static Context* context; + static Context *context; -public: - CLI2 cli2 {}; - std::string home_dir {}; - File rc_file {"~/.taskrc"}; - Path data_dir {"~/.task"}; - Configuration config {}; - TDB2 tdb2 {}; - Hooks hooks {}; - bool determine_color_use {true}; - bool use_color {true}; - bool run_gc {true}; - bool verbosity_legacy {false}; - std::set verbosity {}; - std::vector headers {}; - std::vector footnotes {}; - std::vector errors {}; - std::vector debugMessages {}; - std::map commands {}; - std::map columns {}; - int terminal_width {0}; - int terminal_height {0}; + public: + CLI2 cli2{}; + std::string home_dir{}; + File rc_file{"~/.taskrc"}; + Path data_dir{"~/.task"}; + Configuration config{}; + TDB2 tdb2{}; + Hooks hooks{}; + bool determine_color_use{true}; + bool use_color{true}; + bool verbosity_legacy{false}; + std::set verbosity{}; + std::vector headers{}; + std::vector footnotes{}; + std::vector errors{}; + std::vector debugMessages{}; + std::map commands{}; + std::map columns{}; + int terminal_width{0}; + int terminal_height{0}; - Timer timer_total {}; - long time_total_us {0}; - long time_init_us {0}; - long time_load_us {0}; - long time_gc_us {0}; - long time_filter_us {0}; - long time_commit_us {0}; - long time_sort_us {0}; - long time_render_us {0}; - long time_hooks_us {0}; + Timer timer_total{}; + long time_total_us{0}; + long time_init_us{0}; + long time_load_us{0}; + long time_gc_us{0}; + long time_filter_us{0}; + long time_commit_us{0}; + long time_sort_us{0}; + long time_render_us{0}; + long time_hooks_us{0}; // the current task for DOM references, or NULL if there is no task - const Task * currentTask {NULL}; + const Task *currentTask{NULL}; }; //////////////////////////////////////////////////////////////////////////////// // CurrentTask resets Context::currentTask to previous context task on destruction; this ensures // that this context value is restored when exiting the scope where the context was applied. class CurrentTask { -public: + public: ~CurrentTask(); -private: + private: CurrentTask(Context &context, const Task *previous); Context &context; diff --git a/src/DOM.cpp b/src/DOM.cpp index 4b6f901dc..31a8e5dc0 100644 --- a/src/DOM.cpp +++ b/src/DOM.cpp @@ -25,19 +25,22 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include +#include #include #include -#include +#include +#include #include +#include +#include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// // DOM Supported References: // @@ -60,23 +63,18 @@ // system.version // system.os // -bool getDOM (const std::string& name, Variant& value) -{ +bool getDOM(const std::string& name, Variant& value) { // Special case, blank refs cause problems. - if (name == "") - return false; + if (name == "") return false; - auto len = name.length (); + auto len = name.length(); // rc. --> context.config - if (len > 3 && - ! name.compare (0, 3, "rc.", 3)) - { - auto key = name.substr (3); - auto c = Context::getContext ().config.find (key); - if (c != Context::getContext ().config.end ()) - { - value = Variant (c->second); + if (len > 3 && !name.compare(0, 3, "rc.", 3)) { + auto key = name.substr(3); + auto c = Context::getContext().config.find(key); + if (c != Context::getContext().config.end()) { + value = Variant(c->second); return true; } @@ -84,55 +82,41 @@ bool getDOM (const std::string& name, Variant& value) } // tw.* - if (len > 3 && - ! name.compare (0, 3, "tw.", 3)) - { - if (name == "tw.syncneeded") - { - value = Variant (0); - if (Context::getContext ().tdb2.num_local_changes () > 0) { - value = Variant (1); + if (len > 3 && !name.compare(0, 3, "tw.", 3)) { + if (name == "tw.syncneeded") { + value = Variant(0); + if (Context::getContext().tdb2.num_local_changes() > 0) { + value = Variant(1); } return true; - } - else if (name == "tw.program") - { - value = Variant (Context::getContext ().cli2.getBinary ()); + } else if (name == "tw.program") { + value = Variant(Context::getContext().cli2.getBinary()); return true; - } - else if (name == "tw.args") - { + } else if (name == "tw.args") { std::string commandLine; - for (auto& arg : Context::getContext ().cli2._original_args) - { - if (commandLine != "") - commandLine += ' '; + for (auto& arg : Context::getContext().cli2._original_args) { + if (commandLine != "") commandLine += ' '; commandLine += arg.attribute("raw"); } - value = Variant (commandLine); + value = Variant(commandLine); return true; - } - else if (name == "tw.width") - { - value = Variant (static_cast (Context::getContext ().terminal_width - ? Context::getContext ().terminal_width - : Context::getContext ().getWidth ())); + } else if (name == "tw.width") { + value = Variant(static_cast(Context::getContext().terminal_width + ? Context::getContext().terminal_width + : Context::getContext().getWidth())); return true; - } - else if (name == "tw.height") - { - value = Variant (static_cast (Context::getContext ().terminal_height - ? Context::getContext ().terminal_height - : Context::getContext ().getHeight ())); + } else if (name == "tw.height") { + value = Variant(static_cast(Context::getContext().terminal_height + ? Context::getContext().terminal_height + : Context::getContext().getHeight())); return true; } - else if (name == "tw.version") - { - value = Variant (VERSION); + else if (name == "tw.version") { + value = Variant(VERSION); return true; } @@ -140,40 +124,29 @@ bool getDOM (const std::string& name, Variant& value) } // context.* - if (len > 8 && - ! name.compare (0, 8, "context.", 8)) - { - if (name == "context.program") - { - value = Variant (Context::getContext ().cli2.getBinary ()); + if (len > 8 && !name.compare(0, 8, "context.", 8)) { + if (name == "context.program") { + value = Variant(Context::getContext().cli2.getBinary()); return true; - } - else if (name == "context.args") - { + } else if (name == "context.args") { std::string commandLine; - for (auto& arg : Context::getContext ().cli2._original_args) - { - if (commandLine != "") - commandLine += ' '; + for (auto& arg : Context::getContext().cli2._original_args) { + if (commandLine != "") commandLine += ' '; commandLine += arg.attribute("raw"); } - value = Variant (commandLine); + value = Variant(commandLine); return true; - } - else if (name == "context.width") - { - value = Variant (static_cast (Context::getContext ().terminal_width - ? Context::getContext ().terminal_width - : Context::getContext ().getWidth ())); + } else if (name == "context.width") { + value = Variant(static_cast(Context::getContext().terminal_width + ? Context::getContext().terminal_width + : Context::getContext().getWidth())); return true; - } - else if (name == "context.height") - { - value = Variant (static_cast (Context::getContext ().terminal_height - ? Context::getContext ().terminal_height - : Context::getContext ().getHeight ())); + } else if (name == "context.height") { + value = Variant(static_cast(Context::getContext().terminal_height + ? Context::getContext().terminal_height + : Context::getContext().getHeight())); return true; } @@ -181,20 +154,16 @@ bool getDOM (const std::string& name, Variant& value) } // system. --> Implement locally. - if (len > 7 && - ! name.compare (0, 7, "system.", 7)) - { + if (len > 7 && !name.compare(0, 7, "system.", 7)) { // Taskwarrior version number. - if (name == "system.version") - { - value = Variant (VERSION); + if (name == "system.version") { + value = Variant(VERSION); return true; } // OS type. - else if (name == "system.os") - { - value = Variant (osName ()); + else if (name == "system.os") { + value = Variant(osName()); return true; } @@ -237,210 +206,199 @@ bool getDOM (const std::string& name, Variant& value) // // If task is NULL, then the contextual task will be determined from the DOM // string, if any exists. -bool getDOM (const std::string& name, const Task* task, Variant& value) -{ +bool getDOM(const std::string& name, const Task* task, Variant& value) { // Special case, blank refs cause problems. - if (name == "") - return false; + if (name == "") return false; // Quickly deal with the most common cases. - if (task && name == "id") - { - value = Variant (static_cast (task->id)); + if (task && name == "id") { + value = Variant(static_cast(task->id)); return true; } - if (task && name == "urgency") - { - value = Variant (task->urgency_c ()); + if (task && name == "urgency") { + value = Variant(task->urgency_c()); return true; } // split name on '.' - auto elements = split (name, '.'); + auto elements = split(name, '.'); Task loaded_task; // decide whether the reference is going to be the passed // "task" or whether it's going to be a newly loaded task (if id/uuid was // given). const Task* ref = task; - Lexer lexer (elements[0]); + Lexer lexer(elements[0]); std::string token; Lexer::Type type; // If this can be ID/UUID reference (the name contains '.'), // lex it to figure out. Otherwise don't lex, as lexing can be slow. - if ((elements.size() > 1) and lexer.token (token, type)) - { + if ((elements.size() > 1) and lexer.token(token, type)) { bool reloaded = false; - if (type == Lexer::Type::uuid && - token.length () == elements[0].length ()) - { - if (!task || token != task->get ("uuid")) - { - if (Context::getContext ().tdb2.get (token, loaded_task)) - reloaded = true; + if (type == Lexer::Type::uuid && token.length() == elements[0].length()) { + if (!task || token != task->get("uuid")) { + if (Context::getContext().tdb2.get(token, loaded_task)) reloaded = true; } // Eat elements[0]/UUID. - elements.erase (elements.begin ()); - } - else if (type == Lexer::Type::number && - token.find ('.') == std::string::npos) - { - auto id = strtol (token.c_str (), nullptr, 10); - if (id && (!task || id != task->id)) - { - if (Context::getContext ().tdb2.get (id, loaded_task)) - reloaded = true; + elements.erase(elements.begin()); + } else if (type == Lexer::Type::number && token.find('.') == std::string::npos) { + auto id = strtol(token.c_str(), nullptr, 10); + if (id && (!task || id != task->id)) { + if (Context::getContext().tdb2.get(id, loaded_task)) reloaded = true; } // Eat elements[0]/ID. - elements.erase (elements.begin ()); + elements.erase(elements.begin()); } - if (reloaded) - ref = &loaded_task; + if (reloaded) ref = &loaded_task; } - // The remainder of this method requires a contextual task, so if we do not // have one, delegate to the two-argument getDOM - if (!ref) - return getDOM (name, value); + if (!ref) return getDOM(name, value); - auto size = elements.size (); + auto size = elements.size(); std::string canonical; - if ((size == 1 || size == 2) && Context::getContext ().cli2.canonicalize (canonical, "attribute", elements[0])) - { + if ((size == 1 || size == 2) && + Context::getContext().cli2.canonicalize(canonical, "attribute", elements[0])) { // Now that 'ref' is the contextual task, and any ID/UUID is chopped off the // elements vector, DOM resolution is now simple. - if (size == 1 && canonical == "id") - { - value = Variant (static_cast (ref->id)); + if (size == 1 && canonical == "id") { + value = Variant(static_cast(ref->id)); return true; } - if (size == 1 && canonical == "urgency") - { - value = Variant (ref->urgency_c ()); + if (size == 1 && canonical == "urgency") { + value = Variant(ref->urgency_c()); return true; } // Special handling of status required for virtual waiting status // implementation. Remove in 3.0.0. - if (size == 1 && canonical == "status") - { - value = Variant (ref->statusToText (ref->getStatus ())); + if (size == 1 && canonical == "status") { + value = Variant(ref->statusToText(ref->getStatus())); return true; } - Column* column = Context::getContext ().columns[canonical]; + // The "tags" property is deprecated, but it is documented as part of the DOM, so simulate it. + if (size == 1 && canonical == "tags") { + auto tags = ref->getTags(); + value = Variant(join(",", tags)); + return true; + } - if (size == 1 && column) - { - if (column->is_uda () && ! ref->has (canonical)) - { - value = Variant (""); + Column* column = Context::getContext().columns[canonical]; + + if (size == 1 && column) { + if (column->is_uda() && !ref->has(canonical)) { + value = Variant(""); return true; } - if (column->type () == "date") - { - auto numeric = ref->get_date (canonical); + if (column->type() == "date") { + auto numeric = ref->get_date(canonical); if (numeric == 0) - value = Variant (""); + value = Variant(""); else - value = Variant (numeric, Variant::type_date); - } - else if (column->type () == "duration" || canonical == "recur") - { - auto period = ref->get (canonical); + value = Variant(numeric, Variant::type_date); + } else if (column->type() == "duration" || canonical == "recur") { + auto period = ref->get(canonical); Duration iso; std::string::size_type cursor = 0; - if (iso.parse (period, cursor)) - value = Variant (iso.toTime_t (), Variant::type_duration); + if (iso.parse(period, cursor)) + value = Variant(iso.toTime_t(), Variant::type_duration); else - value = Variant (Duration (ref->get (canonical)).toTime_t (), Variant::type_duration); - } - else if (column->type () == "numeric") - value = Variant (ref->get_float (canonical)); + value = Variant(Duration(ref->get(canonical)).toTime_t(), Variant::type_duration); + } else if (column->type() == "numeric") + value = Variant(ref->get_float(canonical)); else - value = Variant (ref->get (canonical)); + value = Variant(ref->get(canonical)); return true; } - if (size == 2 && canonical == "tags") - { - value = Variant (ref->hasTag (elements[1]) ? elements[1] : ""); + if (size == 2 && canonical == "tags") { + value = Variant(ref->hasTag(elements[1]) ? elements[1] : ""); return true; } - if (size == 2 && column && column->type () == "date") - { - Datetime date (ref->get_date (canonical)); - if (elements[1] == "year") { value = Variant (static_cast (date.year ())); return true; } - else if (elements[1] == "month") { value = Variant (static_cast (date.month ())); return true; } - else if (elements[1] == "day") { value = Variant (static_cast (date.day ())); return true; } - else if (elements[1] == "week") { value = Variant (static_cast (date.week ())); return true; } - else if (elements[1] == "weekday") { value = Variant (static_cast (date.dayOfWeek ())); return true; } - else if (elements[1] == "julian") { value = Variant (static_cast (date.dayOfYear ())); return true; } - else if (elements[1] == "hour") { value = Variant (static_cast (date.hour ())); return true; } - else if (elements[1] == "minute") { value = Variant (static_cast (date.minute ())); return true; } - else if (elements[1] == "second") { value = Variant (static_cast (date.second ())); return true; } + if (size == 2 && column && column->type() == "date") { + Datetime date(ref->get_date(canonical)); + if (elements[1] == "year") { + value = Variant(static_cast(date.year())); + return true; + } else if (elements[1] == "month") { + value = Variant(static_cast(date.month())); + return true; + } else if (elements[1] == "day") { + value = Variant(static_cast(date.day())); + return true; + } else if (elements[1] == "week") { + value = Variant(static_cast(date.week())); + return true; + } else if (elements[1] == "weekday") { + value = Variant(static_cast(date.dayOfWeek())); + return true; + } else if (elements[1] == "julian") { + value = Variant(static_cast(date.dayOfYear())); + return true; + } else if (elements[1] == "hour") { + value = Variant(static_cast(date.hour())); + return true; + } else if (elements[1] == "minute") { + value = Variant(static_cast(date.minute())); + return true; + } else if (elements[1] == "second") { + value = Variant(static_cast(date.second())); + return true; + } } } - if (size == 2 && elements[0] == "annotations" && elements[1] == "count") - { - value = Variant (static_cast (ref->getAnnotationCount ())); + if (size == 2 && elements[0] == "annotations" && elements[1] == "count") { + value = Variant(static_cast(ref->getAnnotationCount())); return true; } - if (size == 3 && elements[0] == "annotations") - { - auto annos = ref->getAnnotations (); + if (size == 3 && elements[0] == "annotations") { + auto annos = ref->getAnnotations(); - int a = strtol (elements[1].c_str (), nullptr, 10); + int a = strtol(elements[1].c_str(), nullptr, 10); int count = 0; // Count off the 'a'th annotation. - for (const auto& i : annos) - { - if (++count == a) - { - if (elements[2] == "entry") - { + for (const auto& i : annos) { + if (++count == a) { + if (elements[2] == "entry") { // annotation_1234567890 // 0 ^11 - value = Variant ((time_t) strtoll (i.first.substr (11).c_str (), NULL, 10), Variant::type_date); + value = + Variant((time_t)strtoll(i.first.substr(11).c_str(), NULL, 10), Variant::type_date); return true; - } - else if (elements[2] == "description") - { - value = Variant (i.second); + } else if (elements[2] == "description") { + value = Variant(i.second); return true; } } } } - if (size == 4 && elements[0] == "annotations" && elements[2] == "entry") - { - auto annos = ref->getAnnotations (); + if (size == 4 && elements[0] == "annotations" && elements[2] == "entry") { + auto annos = ref->getAnnotations(); - int a = strtol (elements[1].c_str (), nullptr, 10); + int a = strtol(elements[1].c_str(), nullptr, 10); int count = 0; // Count off the 'a'th annotation. - for (const auto& i : annos) - { - if (++count == a) - { + for (const auto& i : annos) { + if (++count == a) { // ..entry.year // ..entry.month // ..entry.day @@ -450,22 +408,41 @@ bool getDOM (const std::string& name, const Task* task, Variant& value) // ..entry.hour // ..entry.minute // ..entry.second - Datetime date (i.first.substr (11)); - if (elements[3] == "year") { value = Variant (static_cast (date.year ())); return true; } - else if (elements[3] == "month") { value = Variant (static_cast (date.month ())); return true; } - else if (elements[3] == "day") { value = Variant (static_cast (date.day ())); return true; } - else if (elements[3] == "week") { value = Variant (static_cast (date.week ())); return true; } - else if (elements[3] == "weekday") { value = Variant (static_cast (date.dayOfWeek ())); return true; } - else if (elements[3] == "julian") { value = Variant (static_cast (date.dayOfYear ())); return true; } - else if (elements[3] == "hour") { value = Variant (static_cast (date.hour ())); return true; } - else if (elements[3] == "minute") { value = Variant (static_cast (date.minute ())); return true; } - else if (elements[3] == "second") { value = Variant (static_cast (date.second ())); return true; } + Datetime date(i.first.substr(11)); + if (elements[3] == "year") { + value = Variant(static_cast(date.year())); + return true; + } else if (elements[3] == "month") { + value = Variant(static_cast(date.month())); + return true; + } else if (elements[3] == "day") { + value = Variant(static_cast(date.day())); + return true; + } else if (elements[3] == "week") { + value = Variant(static_cast(date.week())); + return true; + } else if (elements[3] == "weekday") { + value = Variant(static_cast(date.dayOfWeek())); + return true; + } else if (elements[3] == "julian") { + value = Variant(static_cast(date.dayOfYear())); + return true; + } else if (elements[3] == "hour") { + value = Variant(static_cast(date.hour())); + return true; + } else if (elements[3] == "minute") { + value = Variant(static_cast(date.minute())); + return true; + } else if (elements[3] == "second") { + value = Variant(static_cast(date.second())); + return true; + } } } } // Delegate to the context-free version of DOM::get. - return getDOM (name, value); + return getDOM(name, value); } //////////////////////////////////////////////////////////////////////////////// @@ -511,41 +488,28 @@ bool getDOM (const std::string& name, const Task* task, Variant& value) // This makes the DOM class a reusible object. //////////////////////////////////////////////////////////////////////////////// -DOM::~DOM () -{ - delete _node; +DOM::~DOM() { delete _node; } + +//////////////////////////////////////////////////////////////////////////////// +void DOM::addSource(const std::string& reference, bool (*provider)(const std::string&, Variant&)) { + if (_node == nullptr) _node = new DOM::Node(); + + _node->addSource(reference, provider); } //////////////////////////////////////////////////////////////////////////////// -void DOM::addSource ( - const std::string& reference, - bool (*provider)(const std::string&, Variant&)) -{ - if (_node == nullptr) - _node = new DOM::Node (); - - _node->addSource (reference, provider); +bool DOM::valid(const std::string& reference) const { + return _node && _node->find(reference) != nullptr; } //////////////////////////////////////////////////////////////////////////////// -bool DOM::valid (const std::string& reference) const -{ - return _node && _node->find (reference) != nullptr; -} +Variant DOM::get(const std::string& reference) const { + Variant v(""); -//////////////////////////////////////////////////////////////////////////////// -Variant DOM::get (const std::string& reference) const -{ - Variant v (""); - - if (_node) - { - auto node = _node->find (reference); - if (node != nullptr && - node->_provider != nullptr) - { - if (node->_provider (reference, v)) - return v; + if (_node) { + auto node = _node->find(reference); + if (node != nullptr && node->_provider != nullptr) { + if (node->_provider(reference, v)) return v; } } @@ -553,60 +517,47 @@ Variant DOM::get (const std::string& reference) const } //////////////////////////////////////////////////////////////////////////////// -int DOM::count () const -{ - if (_node) - return _node->count (); +int DOM::count() const { + if (_node) return _node->count(); return 0; } //////////////////////////////////////////////////////////////////////////////// -std::vector DOM::decomposeReference (const std::string& reference) -{ - return split (reference, '.'); +std::vector DOM::decomposeReference(const std::string& reference) { + return split(reference, '.'); } //////////////////////////////////////////////////////////////////////////////// -std::string DOM::dump () const -{ - if (_node) - return _node->dump (); +std::string DOM::dump() const { + if (_node) return _node->dump(); return ""; } //////////////////////////////////////////////////////////////////////////////// -DOM::Node::~Node () -{ - for (auto& branch : _branches) - delete branch; +DOM::Node::~Node() { + for (auto& branch : _branches) delete branch; } //////////////////////////////////////////////////////////////////////////////// -void DOM::Node::addSource ( - const std::string& reference, - bool (*provider)(const std::string&, Variant&)) -{ +void DOM::Node::addSource(const std::string& reference, + bool (*provider)(const std::string&, Variant&)) { auto cursor = this; - for (const auto& element : DOM::decomposeReference (reference)) - { - auto found {false}; - for (auto& branch : cursor->_branches) - { - if (branch->_name == element) - { + for (const auto& element : DOM::decomposeReference(reference)) { + auto found{false}; + for (auto& branch : cursor->_branches) { + if (branch->_name == element) { cursor = branch; found = true; break; } } - if (! found) - { - auto branch = new DOM::Node (); + if (!found) { + auto branch = new DOM::Node(); branch->_name = element; - cursor->_branches.push_back (branch); + cursor->_branches.push_back(branch); cursor = branch; } } @@ -616,85 +567,66 @@ void DOM::Node::addSource ( //////////////////////////////////////////////////////////////////////////////// // A valid reference is one that has a provider function. -bool DOM::Node::valid (const std::string& reference) const -{ - return find (reference) != nullptr; -} +bool DOM::Node::valid(const std::string& reference) const { return find(reference) != nullptr; } //////////////////////////////////////////////////////////////////////////////// -const DOM::Node* DOM::Node::find (const std::string& reference) const -{ +const DOM::Node* DOM::Node::find(const std::string& reference) const { auto cursor = this; - for (const auto& element : DOM::decomposeReference (reference)) - { - auto found {false}; - for (auto& branch : cursor->_branches) - { - if (branch->_name == element) - { + for (const auto& element : DOM::decomposeReference(reference)) { + auto found{false}; + for (auto& branch : cursor->_branches) { + if (branch->_name == element) { cursor = branch; found = true; break; } } - if (! found) - break; + if (!found) break; } - if (reference.length () && cursor != this) - return cursor; + if (reference.length() && cursor != this) return cursor; return nullptr; } //////////////////////////////////////////////////////////////////////////////// -int DOM::Node::count () const -{ +int DOM::Node::count() const { // Recurse and count the branches. - int total {0}; - for (auto& branch : _branches) - { - if (branch->_provider) - ++total; - total += branch->count (); + int total{0}; + for (auto& branch : _branches) { + if (branch->_provider) ++total; + total += branch->count(); } return total; } //////////////////////////////////////////////////////////////////////////////// -std::string DOM::Node::dumpNode ( - const DOM::Node* node, - int depth) const -{ +std::string DOM::Node::dumpNode(const DOM::Node* node, int depth) const { std::stringstream out; // Indent. - out << std::string (depth * 2, ' '); + out << std::string(depth * 2, ' '); out << "\033[31m" << node->_name << "\033[0m"; - if (node->_provider) - out << " 0x" << std::hex << (long long) (void*) node->_provider; + if (node->_provider) out << " 0x" << std::hex << (long long)(void*)node->_provider; out << '\n'; // Recurse for branches. - for (auto& b : node->_branches) - out << dumpNode (b, depth + 1); + for (auto& b : node->_branches) out << dumpNode(b, depth + 1); - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// -std::string DOM::Node::dump () const -{ +std::string DOM::Node::dump() const { std::stringstream out; - out << "DOM::Node (" << count () << " nodes)\n" - << dumpNode (this, 1); + out << "DOM::Node (" << count() << " nodes)\n" << dumpNode(this, 1); - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/DOM.h b/src/DOM.h index e3595e411..3c2bd3f79 100644 --- a/src/DOM.h +++ b/src/DOM.h @@ -27,49 +27,48 @@ #ifndef INCLUDED_DOM #define INCLUDED_DOM -#include -#include #include +#include + +#include // 2017-04-22 Deprecated, use DOM::get. -bool getDOM (const std::string&, Variant&); -bool getDOM (const std::string&, const Task*, Variant&); +bool getDOM(const std::string&, Variant&); +bool getDOM(const std::string&, const Task*, Variant&); -class DOM -{ -public: - ~DOM (); - void addSource (const std::string&, bool (*)(const std::string&, Variant&)); - bool valid (const std::string&) const; -/* - // TODO Task object should register a generic provider. - Variant get (const Task&, const std::string&) const; -*/ - Variant get (const std::string&) const; - int count () const; - static std::vector decomposeReference (const std::string&); - std::string dump () const; +class DOM { + public: + ~DOM(); + void addSource(const std::string&, bool (*)(const std::string&, Variant&)); + bool valid(const std::string&) const; + /* + // TODO Task object should register a generic provider. + Variant get (const Task&, const std::string&) const; + */ + Variant get(const std::string&) const; + int count() const; + static std::vector decomposeReference(const std::string&); + std::string dump() const; -private: - class Node - { - public: - ~Node (); - void addSource (const std::string&, bool (*)(const std::string&, Variant&)); - bool valid (const std::string&) const; - const DOM::Node* find (const std::string&) const; - int count () const; - std::string dumpNode (const DOM::Node*, int) const; - std::string dump () const; + private: + class Node { + public: + ~Node(); + void addSource(const std::string&, bool (*)(const std::string&, Variant&)); + bool valid(const std::string&) const; + const DOM::Node* find(const std::string&) const; + int count() const; + std::string dumpNode(const DOM::Node*, int) const; + std::string dump() const; - public: - std::string _name {"Unknown"}; - bool (*_provider)(const std::string&, Variant&) {nullptr}; - std::vector _branches {}; + public: + std::string _name{"Unknown"}; + bool (*_provider)(const std::string&, Variant&){nullptr}; + std::vector _branches{}; }; -private: - DOM::Node* _node {nullptr}; + private: + DOM::Node* _node{nullptr}; }; #endif diff --git a/src/Eval.cpp b/src/Eval.cpp index 5319a0804..7d82224ab 100644 --- a/src/Eval.cpp +++ b/src/Eval.cpp @@ -25,75 +25,77 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include -#include +#include +#include +#include +#include #include +#include + +#include //////////////////////////////////////////////////////////////////////////////// // Supported operators, borrowed from C++, particularly the precedence. // Note: table is sorted by length of operator string, so searches match // longest first. -static struct -{ +static struct { std::string op; - int precedence; - char type; // b=binary, u=unary, c=circumfix - char associativity; // l=left, r=right, _=? -} operators[] = -{ - // Operator Precedence Type Associativity - { "^", 16, 'b', 'r' }, // Exponent + int precedence; + char type; // b=binary, u=unary, c=circumfix + char associativity; // l=left, r=right, _=? +} operators[] = { + // Operator Precedence Type Associativity + {"^", 16, 'b', 'r'}, // Exponent - { "!", 15, 'u', 'r' }, // Unary not - { "_neg_", 15, 'u', 'r' }, // Unary minus - { "_pos_", 15, 'u', 'r' }, // Unary plus + {"!", 15, 'u', 'r'}, // Unary not + {"_neg_", 15, 'u', 'r'}, // Unary minus + {"_pos_", 15, 'u', 'r'}, // Unary plus - { "_hastag_", 14, 'b', 'l'}, // +tag [Pseudo-op] - { "_notag_", 14, 'b', 'l'}, // -tag [Pseudo-op] + {"_hastag_", 14, 'b', 'l'}, // +tag [Pseudo-op] + {"_notag_", 14, 'b', 'l'}, // -tag [Pseudo-op] - { "*", 13, 'b', 'l' }, // Multiplication - { "/", 13, 'b', 'l' }, // Division - { "%", 13, 'b', 'l' }, // Modulus + {"*", 13, 'b', 'l'}, // Multiplication + {"/", 13, 'b', 'l'}, // Division + {"%", 13, 'b', 'l'}, // Modulus - { "+", 12, 'b', 'l' }, // Addition - { "-", 12, 'b', 'l' }, // Subtraction + {"+", 12, 'b', 'l'}, // Addition + {"-", 12, 'b', 'l'}, // Subtraction - { "<=", 10, 'b', 'l' }, // Less than or equal - { ">=", 10, 'b', 'l' }, // Greater than or equal - { ">", 10, 'b', 'l' }, // Greater than - { "<", 10, 'b', 'l' }, // Less than + {"<=", 10, 'b', 'l'}, // Less than or equal + {">=", 10, 'b', 'l'}, // Greater than or equal + {">", 10, 'b', 'l'}, // Greater than + {"<", 10, 'b', 'l'}, // Less than - { "=", 9, 'b', 'l' }, // Equal (partial) - { "==", 9, 'b', 'l' }, // Equal (exact) - { "!=", 9, 'b', 'l' }, // Inequal (partial) - { "!==", 9, 'b', 'l' }, // Inequal (exact) + {"=", 9, 'b', 'l'}, // Equal (partial) + {"==", 9, 'b', 'l'}, // Equal (exact) + {"!=", 9, 'b', 'l'}, // Inequal (partial) + {"!==", 9, 'b', 'l'}, // Inequal (exact) - { "~", 8, 'b', 'l' }, // Regex match - { "!~", 8, 'b', 'l' }, // Regex non-match + {"~", 8, 'b', 'l'}, // Regex match + {"!~", 8, 'b', 'l'}, // Regex non-match - { "and", 5, 'b', 'l' }, // Conjunction - { "or", 4, 'b', 'l' }, // Disjunction - { "xor", 3, 'b', 'l' }, // Disjunction + {"and", 5, 'b', 'l'}, // Conjunction + {"or", 4, 'b', 'l'}, // Disjunction + {"xor", 3, 'b', 'l'}, // Disjunction - { "(", 0, 'c', '_' }, // Precedence start - { ")", 0, 'c', '_' }, // Precedence end + {"(", 0, 'c', '_'}, // Precedence start + {")", 0, 'c', '_'}, // Precedence end }; -#define NUM_OPERATORS (sizeof (operators) / sizeof (operators[0])) +#define NUM_OPERATORS (sizeof(operators) / sizeof(operators[0])) //////////////////////////////////////////////////////////////////////////////// // Built-in support for some named constants. -static bool namedConstants (const std::string& name, Variant& value) -{ - if (name == "true") value = Variant (true); - else if (name == "false") value = Variant (false); - else if (name == "pi") value = Variant (3.14159165); +static bool namedConstants(const std::string& name, Variant& value) { + if (name == "true") + value = Variant(true); + else if (name == "false") + value = Variant(false); + else if (name == "pi") + value = Variant(3.14159165); else return false; @@ -102,11 +104,9 @@ static bool namedConstants (const std::string& name, Variant& value) //////////////////////////////////////////////////////////////////////////////// // Support for evaluating DOM references (add with `e.AddSource(domSource)`) -bool domSource (const std::string& identifier, Variant& value) -{ - if (getDOM (identifier, Context::getContext ().currentTask, value)) - { - value.source (identifier); +bool domSource(const std::string& identifier, Variant& value) { + if (getDOM(identifier, Context::getContext().currentTask, value)) { + value.source(identifier); return true; } @@ -114,300 +114,273 @@ bool domSource (const std::string& identifier, Variant& value) } //////////////////////////////////////////////////////////////////////////////// -Eval::Eval () -{ - addSource (namedConstants); -} +Eval::Eval() { addSource(namedConstants); } //////////////////////////////////////////////////////////////////////////////// -void Eval::addSource (bool (*source)(const std::string&, Variant&)) -{ - _sources.push_back (source); -} +void Eval::addSource(bool (*source)(const std::string&, Variant&)) { _sources.push_back(source); } //////////////////////////////////////////////////////////////////////////////// -void Eval::evaluateInfixExpression (const std::string& e, Variant& v) const -{ +void Eval::evaluateInfixExpression(const std::string& e, Variant& v) const { // Reduce e to a vector of tokens. - Lexer l (e); - std::vector > tokens; + Lexer l(e); + std::vector> tokens; std::string token; Lexer::Type type; - while (l.token (token, type)) - tokens.emplace_back (token, type); + while (l.token(token, type)) tokens.emplace_back(token, type); // Parse for syntax checking and operator replacement. - if (_debug) - Context::getContext ().debug ("FILTER Infix " + dump (tokens)); - infixParse (tokens); - if (_debug) - Context::getContext ().debug ("FILTER Infix parsed " + dump (tokens)); + if (_debug) Context::getContext().debug("FILTER Infix " + dump(tokens)); + infixParse(tokens); + if (_debug) Context::getContext().debug("FILTER Infix parsed " + dump(tokens)); // Convert infix --> postfix. - infixToPostfix (tokens); - if (_debug) - Context::getContext ().debug ("FILTER Postfix " + dump (tokens)); + infixToPostfix(tokens); + if (_debug) Context::getContext().debug("FILTER Postfix " + dump(tokens)); // Call the postfix evaluator. - evaluatePostfixStack (tokens, v); + evaluatePostfixStack(tokens, v); } //////////////////////////////////////////////////////////////////////////////// -void Eval::evaluatePostfixExpression (const std::string& e, Variant& v) const -{ +void Eval::evaluatePostfixExpression(const std::string& e, Variant& v) const { // Reduce e to a vector of tokens. - Lexer l (e); - std::vector > tokens; + Lexer l(e); + std::vector> tokens; std::string token; Lexer::Type type; - while (l.token (token, type)) - tokens.emplace_back (token, type); + while (l.token(token, type)) tokens.emplace_back(token, type); - if (_debug) - Context::getContext ().debug ("FILTER Postfix " + dump (tokens)); + if (_debug) Context::getContext().debug("FILTER Postfix " + dump(tokens)); // Call the postfix evaluator. - evaluatePostfixStack (tokens, v); + evaluatePostfixStack(tokens, v); } //////////////////////////////////////////////////////////////////////////////// -void Eval::compileExpression ( - const std::vector >& precompiled) -{ +void Eval::compileExpression(const std::vector>& precompiled) { _compiled = precompiled; // Parse for syntax checking and operator replacement. - if (_debug) - Context::getContext ().debug ("FILTER Infix " + dump (_compiled)); - infixParse (_compiled); - if (_debug) - Context::getContext ().debug ("FILTER Infix parsed " + dump (_compiled)); + if (_debug) Context::getContext().debug("FILTER Infix " + dump(_compiled)); + infixParse(_compiled); + if (_debug) Context::getContext().debug("FILTER Infix parsed " + dump(_compiled)); // Convert infix --> postfix. - infixToPostfix (_compiled); - if (_debug) - Context::getContext ().debug ("FILTER Postfix " + dump (_compiled)); + infixToPostfix(_compiled); + if (_debug) Context::getContext().debug("FILTER Postfix " + dump(_compiled)); } //////////////////////////////////////////////////////////////////////////////// -void Eval::evaluateCompiledExpression (Variant& v) -{ +void Eval::evaluateCompiledExpression(Variant& v) { // Call the postfix evaluator. - evaluatePostfixStack (_compiled, v); + evaluatePostfixStack(_compiled, v); } //////////////////////////////////////////////////////////////////////////////// -void Eval::debug (bool value) -{ - _debug = value; -} +void Eval::debug(bool value) { _debug = value; } //////////////////////////////////////////////////////////////////////////////// // Static. -std::vector Eval::getOperators () -{ - std::vector all; +std::vector Eval::getOperators() { + std::vector all; all.reserve(NUM_OPERATORS); - for (const auto &opr : operators) - all.push_back (opr.op); + for (const auto& opr : operators) all.push_back(opr.op); return all; } //////////////////////////////////////////////////////////////////////////////// // Static. -std::vector Eval::getBinaryOperators () -{ - std::vector all; - for (const auto &opr : operators) - if (opr.type == 'b') - all.push_back (opr.op); +std::vector Eval::getBinaryOperators() { + std::vector all; + for (const auto& opr : operators) + if (opr.type == 'b') all.push_back(opr.op); return all; } //////////////////////////////////////////////////////////////////////////////// -void Eval::evaluatePostfixStack ( - const std::vector >& tokens, - Variant& result) const -{ - if (tokens.size () == 0) - throw std::string ("No expression to evaluate."); +void Eval::evaluatePostfixStack(const std::vector>& tokens, + Variant& result) const { + if (tokens.size() == 0) throw std::string("No expression to evaluate."); // This is stack used by the postfix evaluator. - std::vector values; + std::vector values; values.reserve(tokens.size()); - for (const auto& token : tokens) - { + for (const auto& token : tokens) { // Unary operators. - if (token.second == Lexer::Type::op && - token.first == "!") - { - if (values.size () < 1) - throw std::string ("The expression could not be evaluated."); + if (token.second == Lexer::Type::op && token.first == "!") { + if (values.size() < 1) throw std::string("The expression could not be evaluated."); - Variant right = values.back (); - values.pop_back (); - Variant result = ! right; - values.push_back (result); + Variant right = values.back(); + values.pop_back(); + Variant result = !right; + values.push_back(result); if (_debug) - Context::getContext ().debug (format ("Eval {1} ↓'{2}' → ↑'{3}'", token.first, (std::string) right, (std::string) result)); - } - else if (token.second == Lexer::Type::op && - token.first == "_neg_") - { - if (values.size () < 1) - throw std::string ("The expression could not be evaluated."); + Context::getContext().debug(format("Eval {1} ↓'{2}' → ↑'{3}'", token.first, + (std::string)right, (std::string)result)); + } else if (token.second == Lexer::Type::op && token.first == "_neg_") { + if (values.size() < 1) throw std::string("The expression could not be evaluated."); - Variant right = values.back (); - values.pop_back (); + Variant right = values.back(); + values.pop_back(); - Variant result (0); + Variant result(0); result -= right; - values.push_back (result); + values.push_back(result); if (_debug) - Context::getContext ().debug (format ("Eval {1} ↓'{2}' → ↑'{3}'", token.first, (std::string) right, (std::string) result)); - } - else if (token.second == Lexer::Type::op && - token.first == "_pos_") - { + Context::getContext().debug(format("Eval {1} ↓'{2}' → ↑'{3}'", token.first, + (std::string)right, (std::string)result)); + } else if (token.second == Lexer::Type::op && token.first == "_pos_") { // The _pos_ operator is a NOP. if (_debug) - Context::getContext ().debug (format ("[{1}] eval op {2} NOP", values.size (), token.first)); + Context::getContext().debug(format("[{1}] eval op {2} NOP", values.size(), token.first)); } // Binary operators. - else if (token.second == Lexer::Type::op) - { - if (values.size () < 2) - throw std::string ("The expression could not be evaluated."); + else if (token.second == Lexer::Type::op) { + if (values.size() < 2) throw std::string("The expression could not be evaluated."); - Variant right = values.back (); - values.pop_back (); + Variant right = values.back(); + values.pop_back(); - Variant left = values.back (); - values.pop_back (); + Variant left = values.back(); + values.pop_back(); - auto contextTask = Context::getContext ().currentTask; + auto contextTask = Context::getContext().currentTask; // Ordering these by anticipation frequency of use is a good idea. Variant result; - if (token.first == "and") result = left && right; - else if (token.first == "or") result = left || right; - else if (token.first == "&&") result = left && right; - else if (token.first == "||") result = left || right; - else if (token.first == "<") result = left < right; - else if (token.first == "<=") result = left <= right; - else if (token.first == ">") result = left > right; - else if (token.first == ">=") result = left >= right; - else if (token.first == "==") result = left.operator== (right); - else if (token.first == "!==") result = left.operator!= (right); - else if (token.first == "=") result = left.operator_partial (right); - else if (token.first == "!=") result = left.operator_nopartial (right); - else if (token.first == "+") result = left + right; - else if (token.first == "-") result = left - right; - else if (token.first == "*") result = left * right; - else if (token.first == "/") result = left / right; - else if (token.first == "^") result = left ^ right; - else if (token.first == "%") result = left % right; - else if (token.first == "xor") result = left.operator_xor (right); + if (token.first == "and") + result = left && right; + else if (token.first == "or") + result = left || right; + else if (token.first == "&&") + result = left && right; + else if (token.first == "||") + result = left || right; + else if (token.first == "<") + result = left < right; + else if (token.first == "<=") + result = left <= right; + else if (token.first == ">") + result = left > right; + else if (token.first == ">=") + result = left >= right; + else if (token.first == "==") + result = left.operator==(right); + else if (token.first == "!==") + result = left.operator!=(right); + else if (token.first == "=") + result = left.operator_partial(right); + else if (token.first == "!=") + result = left.operator_nopartial(right); + else if (token.first == "+") + result = left + right; + else if (token.first == "-") + result = left - right; + else if (token.first == "*") + result = left * right; + else if (token.first == "/") + result = left / right; + else if (token.first == "^") + result = left ^ right; + else if (token.first == "%") + result = left % right; + else if (token.first == "xor") + result = left.operator_xor(right); else if (contextTask) { - if (token.first == "~") result = left.operator_match (right, *contextTask); - else if (token.first == "!~") result = left.operator_nomatch (right, *contextTask); - else if (token.first == "_hastag_") result = left.operator_hastag (right, *contextTask); - else if (token.first == "_notag_") result = left.operator_notag (right, *contextTask); + if (token.first == "~") + result = left.operator_match(right, *contextTask); + else if (token.first == "!~") + result = left.operator_nomatch(right, *contextTask); + else if (token.first == "_hastag_") + result = left.operator_hastag(right, *contextTask); + else if (token.first == "_notag_") + result = left.operator_notag(right, *contextTask); else - throw format ("Unsupported operator '{1}'.", token.first); - } - else - throw format ("Unsupported operator '{1}'.", token.first); + throw format("Unsupported operator '{1}'.", token.first); + } else + throw format("Unsupported operator '{1}'.", token.first); - values.push_back (result); + values.push_back(result); if (_debug) - Context::getContext ().debug (format ("Eval ↓'{1}' {2} ↓'{3}' → ↑'{4}'", (std::string) left, token.first, (std::string) right, (std::string) result)); + Context::getContext().debug(format("Eval ↓'{1}' {2} ↓'{3}' → ↑'{4}'", (std::string)left, + token.first, (std::string)right, (std::string)result)); } // Literals and identifiers. - else - { - Variant v (token.first); - switch (token.second) - { - case Lexer::Type::number: - if (Lexer::isAllDigits (token.first)) - { - v.cast (Variant::type_integer); - if (_debug) - Context::getContext ().debug (format ("Eval literal number ↑'{1}'", (std::string) v)); - } - else - { - v.cast (Variant::type_real); - if (_debug) - Context::getContext ().debug (format ("Eval literal decimal ↑'{1}'", (std::string) v)); - } - break; + else { + Variant v(token.first); + switch (token.second) { + case Lexer::Type::number: + if (Lexer::isAllDigits(token.first)) { + v.cast(Variant::type_integer); + if (_debug) + Context::getContext().debug(format("Eval literal number ↑'{1}'", (std::string)v)); + } else { + v.cast(Variant::type_real); + if (_debug) + Context::getContext().debug(format("Eval literal decimal ↑'{1}'", (std::string)v)); + } + break; - case Lexer::Type::op: - throw std::string ("Operator expected."); - break; + case Lexer::Type::op: + throw std::string("Operator expected."); + break; - case Lexer::Type::dom: - case Lexer::Type::identifier: - { + case Lexer::Type::dom: + case Lexer::Type::identifier: { bool found = false; - for (const auto& source : _sources) - { - if (source (token.first, v)) - { + for (const auto& source : _sources) { + if (source(token.first, v)) { if (_debug) - Context::getContext ().debug (format ("Eval identifier source '{1}' → ↑'{2}'", token.first, (std::string) v)); + Context::getContext().debug( + format("Eval identifier source '{1}' → ↑'{2}'", token.first, (std::string)v)); found = true; break; } } // An identifier that fails lookup is a string. - if (! found) - { - v.cast (Variant::type_string); + if (!found) { + v.cast(Variant::type_string); if (_debug) - Context::getContext ().debug (format ("Eval identifier source failed '{1}'", token.first)); + Context::getContext().debug( + format("Eval identifier source failed '{1}'", token.first)); } - } - break; + } break; - case Lexer::Type::date: - v.cast (Variant::type_date); - if (_debug) - Context::getContext ().debug (format ("Eval literal date ↑'{1}'", (std::string) v)); - break; + case Lexer::Type::date: + v.cast(Variant::type_date); + if (_debug) + Context::getContext().debug(format("Eval literal date ↑'{1}'", (std::string)v)); + break; - case Lexer::Type::duration: - v.cast (Variant::type_duration); - if (_debug) - Context::getContext ().debug (format ("Eval literal duration ↑'{1}'", (std::string) v)); - break; + case Lexer::Type::duration: + v.cast(Variant::type_duration); + if (_debug) + Context::getContext().debug(format("Eval literal duration ↑'{1}'", (std::string)v)); + break; - // Nothing to do. - case Lexer::Type::string: - default: - if (_debug) - Context::getContext ().debug (format ("Eval literal string ↑'{1}'", (std::string) v)); - break; + // Nothing to do. + case Lexer::Type::string: + default: + if (_debug) + Context::getContext().debug(format("Eval literal string ↑'{1}'", (std::string)v)); + break; } - values.push_back (v); + values.push_back(v); } } // If there is more than one variant left on the stack, then the original // expression was not valid. - if (values.size () != 1) - throw std::string ("The value is not an expression."); + if (values.size() != 1) throw std::string("The value is not an expression."); result = values[0]; } @@ -426,31 +399,20 @@ void Eval::evaluatePostfixStack ( // Exponent --> Primitive ["^" Primitive] // Primitive --> "(" Logical ")" | Variant // -void Eval::infixParse ( - std::vector >& infix) const -{ +void Eval::infixParse(std::vector>& infix) const { unsigned int i = 0; - parseLogical (infix, i); + parseLogical(infix, i); } //////////////////////////////////////////////////////////////////////////////// // Logical --> Regex {( "and" | "or" | "xor" ) Regex} -bool Eval::parseLogical ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseRegex (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "and" || - infix[i].first == "or" || - infix[i].first == "xor")) - { +bool Eval::parseLogical(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseRegex(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "and" || infix[i].first == "or" || infix[i].first == "xor")) { ++i; - if (! parseRegex (infix, i)) - return false; + if (!parseRegex(infix, i)) return false; } return true; @@ -461,21 +423,13 @@ bool Eval::parseLogical ( //////////////////////////////////////////////////////////////////////////////// // Regex --> Equality {( "~" | "!~" ) Equality} -bool Eval::parseRegex ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseEquality (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "~" || - infix[i].first == "!~")) - { +bool Eval::parseRegex(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseEquality(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "~" || infix[i].first == "!~")) { ++i; - if (! parseEquality (infix, i)) - return false; + if (!parseEquality(infix, i)) return false; } return true; @@ -486,23 +440,14 @@ bool Eval::parseRegex ( //////////////////////////////////////////////////////////////////////////////// // Equality --> Comparative {( "==" | "=" | "!==" | "!=" ) Comparative} -bool Eval::parseEquality ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseComparative (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "==" || - infix[i].first == "=" || - infix[i].first == "!==" || - infix[i].first == "!=")) - { +bool Eval::parseEquality(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseComparative(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "==" || infix[i].first == "=" || infix[i].first == "!==" || + infix[i].first == "!=")) { ++i; - if (! parseComparative (infix, i)) - return false; + if (!parseComparative(infix, i)) return false; } return true; @@ -513,23 +458,14 @@ bool Eval::parseEquality ( //////////////////////////////////////////////////////////////////////////////// // Comparative --> Arithmetic {( "<=" | "<" | ">=" | ">" ) Arithmetic} -bool Eval::parseComparative ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseArithmetic (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "<=" || - infix[i].first == "<" || - infix[i].first == ">=" || - infix[i].first == ">")) - { +bool Eval::parseComparative(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseArithmetic(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "<=" || infix[i].first == "<" || infix[i].first == ">=" || + infix[i].first == ">")) { ++i; - if (! parseArithmetic (infix, i)) - return false; + if (!parseArithmetic(infix, i)) return false; } return true; @@ -540,21 +476,13 @@ bool Eval::parseComparative ( //////////////////////////////////////////////////////////////////////////////// // Arithmetic --> Geometric {( "+" | "-" ) Geometric} -bool Eval::parseArithmetic ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseGeometric (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "+" || - infix[i].first == "-")) - { +bool Eval::parseArithmetic(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseGeometric(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "+" || infix[i].first == "-")) { ++i; - if (! parseGeometric (infix, i)) - return false; + if (!parseGeometric(infix, i)) return false; } return true; @@ -565,22 +493,13 @@ bool Eval::parseArithmetic ( //////////////////////////////////////////////////////////////////////////////// // Geometric --> Tag {( "*" | "/" | "%" ) Tag} -bool Eval::parseGeometric ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseTag (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "*" || - infix[i].first == "/" || - infix[i].first == "%")) - { +bool Eval::parseGeometric(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseTag(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "*" || infix[i].first == "/" || infix[i].first == "%")) { ++i; - if (! parseTag (infix, i)) - return false; + if (!parseTag(infix, i)) return false; } return true; @@ -591,21 +510,13 @@ bool Eval::parseGeometric ( //////////////////////////////////////////////////////////////////////////////// // Tag --> Unary {( "_hastag_" | "_notag_" ) Unary} -bool Eval::parseTag ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size () && - parseUnary (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - (infix[i].first == "_hastag_" || - infix[i].first == "_notag_")) - { +bool Eval::parseTag(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parseUnary(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && + (infix[i].first == "_hastag_" || infix[i].first == "_notag_")) { ++i; - if (! parseUnary (infix, i)) - return false; + if (!parseUnary(infix, i)) return false; } return true; @@ -616,47 +527,31 @@ bool Eval::parseTag ( //////////////////////////////////////////////////////////////////////////////// // Unary --> [( "-" | "+" | "!" )] Exponent -bool Eval::parseUnary ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size ()) - { - if (infix[i].first == "-") - { +bool Eval::parseUnary(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size()) { + if (infix[i].first == "-") { infix[i].first = "_neg_"; ++i; - } - else if (infix[i].first == "+") - { + } else if (infix[i].first == "+") { infix[i].first = "_pos_"; ++i; - } - else if (infix[i].first == "!") - { + } else if (infix[i].first == "!") { ++i; } } - return parseExponent (infix, i); + return parseExponent(infix, i); } //////////////////////////////////////////////////////////////////////////////// // Exponent --> Primitive ["^" Primitive] -bool Eval::parseExponent ( - std::vector >& infix, - unsigned int& i) const -{ - if (i < infix.size () && - parsePrimitive (infix, i)) - { - while (i < infix.size () && - infix[i].second == Lexer::Type::op && - infix[i].first == "^") - { +bool Eval::parseExponent(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size() && parsePrimitive(infix, i)) { + while (i < infix.size() && infix[i].second == Lexer::Type::op && infix[i].first == "^") { ++i; - if (! parsePrimitive (infix, i)) - return false; + if (!parsePrimitive(infix, i)) return false; } return true; @@ -667,46 +562,31 @@ bool Eval::parseExponent ( //////////////////////////////////////////////////////////////////////////////// // Primitive --> "(" Logical ")" | Variant -bool Eval::parsePrimitive ( - std::vector >& infix, - unsigned int &i) const -{ - if (i < infix.size ()) - { - if (infix[i].first == "(") - { +bool Eval::parsePrimitive(std::vector>& infix, + unsigned int& i) const { + if (i < infix.size()) { + if (infix[i].first == "(") { ++i; - if (i < infix.size () && - parseLogical (infix, i)) - { - if (i < infix.size () && - infix[i].first == ")") - { + if (i < infix.size() && parseLogical(infix, i)) { + if (i < infix.size() && infix[i].first == ")") { ++i; return true; } } - } - else - { + } else { bool found = false; - for (const auto& source : _sources) - { + for (const auto& source : _sources) { Variant v; - if (source (infix[i].first, v)) - { + if (source(infix[i].first, v)) { found = true; break; } } - if (found) - { + if (found) { ++i; return true; - } - else if (infix[i].second != Lexer::Type::op) - { + } else if (infix[i].second != Lexer::Type::op) { ++i; return true; } @@ -748,95 +628,71 @@ bool Eval::parsePrimitive ( // Pop the operator onto the output queue. // Exit. // -void Eval::infixToPostfix ( - std::vector >& infix) const -{ +void Eval::infixToPostfix(std::vector>& infix) const { // Short circuit. - if (infix.size () == 1) - return; + if (infix.size() == 1) return; // Result. - std::vector > postfix; + std::vector> postfix; // Shunting yard. - std::vector > op_stack; + std::vector> op_stack; // Operator characteristics. char type; unsigned int precedence; char associativity; - for (auto& token : infix) - { - if (token.second == Lexer::Type::op && - token.first == "(") - { - op_stack.push_back (token); - } - else if (token.second == Lexer::Type::op && - token.first == ")") - { - while (op_stack.size () && - op_stack.back ().first != "(") - { - postfix.push_back (op_stack.back ()); - op_stack.pop_back (); + for (auto& token : infix) { + if (token.second == Lexer::Type::op && token.first == "(") { + op_stack.push_back(token); + } else if (token.second == Lexer::Type::op && token.first == ")") { + while (op_stack.size() && op_stack.back().first != "(") { + postfix.push_back(op_stack.back()); + op_stack.pop_back(); } - if (op_stack.size ()) - op_stack.pop_back (); + if (op_stack.size()) + op_stack.pop_back(); else - throw std::string ("Mismatched parentheses in expression"); - } - else if (token.second == Lexer::Type::op && - identifyOperator (token.first, type, precedence, associativity)) - { + throw std::string("Mismatched parentheses in expression"); + } else if (token.second == Lexer::Type::op && + identifyOperator(token.first, type, precedence, associativity)) { char type2; unsigned int precedence2; char associativity2; - while (op_stack.size () > 0 && - identifyOperator (op_stack.back ().first, type2, precedence2, associativity2) && + while (op_stack.size() > 0 && + identifyOperator(op_stack.back().first, type2, precedence2, associativity2) && ((associativity == 'l' && precedence <= precedence2) || - (associativity == 'r' && precedence < precedence2))) - { - postfix.push_back (op_stack.back ()); - op_stack.pop_back (); + (associativity == 'r' && precedence < precedence2))) { + postfix.push_back(op_stack.back()); + op_stack.pop_back(); } - op_stack.push_back (token); - } - else - { - postfix.push_back (token); + op_stack.push_back(token); + } else { + postfix.push_back(token); } } - while (op_stack.size ()) - { - if (op_stack.back ().first == "(" || - op_stack.back ().first == ")") - throw std::string ("Mismatched parentheses in expression"); + while (op_stack.size()) { + if (op_stack.back().first == "(" || op_stack.back().first == ")") + throw std::string("Mismatched parentheses in expression"); - postfix.push_back (op_stack.back ()); - op_stack.pop_back (); + postfix.push_back(op_stack.back()); + op_stack.pop_back(); } infix = postfix; } //////////////////////////////////////////////////////////////////////////////// -bool Eval::identifyOperator ( - const std::string& op, - char& type, - unsigned int& precedence, - char& associativity) const -{ - for (const auto& opr : operators) - { - if (opr.op == op) - { - type = opr.type; - precedence = opr.precedence; +bool Eval::identifyOperator(const std::string& op, char& type, unsigned int& precedence, + char& associativity) const { + for (const auto& opr : operators) { + if (opr.op == op) { + type = opr.type; + precedence = opr.precedence; associativity = opr.associativity; return true; } @@ -846,33 +702,29 @@ bool Eval::identifyOperator ( } //////////////////////////////////////////////////////////////////////////////// -std::string Eval::dump ( - std::vector >& tokens) const -{ +std::string Eval::dump(std::vector>& tokens) const { // Set up a color mapping. - std::map color_map; - color_map[Lexer::Type::op] = Color ("gray14 on gray6"); - color_map[Lexer::Type::number] = Color ("rgb530 on gray6"); - color_map[Lexer::Type::hex] = Color ("rgb303 on gray6"); - color_map[Lexer::Type::string] = Color ("rgb550 on gray6"); - color_map[Lexer::Type::dom] = Color ("rgb045 on gray6"); - color_map[Lexer::Type::identifier] = Color ("rgb035 on gray6"); - color_map[Lexer::Type::date] = Color ("rgb150 on gray6"); - color_map[Lexer::Type::duration] = Color ("rgb531 on gray6"); + std::map color_map; + color_map[Lexer::Type::op] = Color("gray14 on gray6"); + color_map[Lexer::Type::number] = Color("rgb530 on gray6"); + color_map[Lexer::Type::hex] = Color("rgb303 on gray6"); + color_map[Lexer::Type::string] = Color("rgb550 on gray6"); + color_map[Lexer::Type::dom] = Color("rgb045 on gray6"); + color_map[Lexer::Type::identifier] = Color("rgb035 on gray6"); + color_map[Lexer::Type::date] = Color("rgb150 on gray6"); + color_map[Lexer::Type::duration] = Color("rgb531 on gray6"); std::string output; - for (auto i = tokens.begin (); i != tokens.end (); ++i) - { - if (i != tokens.begin ()) - output += ' '; + for (auto i = tokens.begin(); i != tokens.end(); ++i) { + if (i != tokens.begin()) output += ' '; Color c; - if (color_map[i->second].nontrivial ()) + if (color_map[i->second].nontrivial()) c = color_map[i->second]; else - c = Color ("rgb000 on gray6"); + c = Color("rgb000 on gray6"); - output += c.colorize (i->first); + output += c.colorize(i->first); } return output; diff --git a/src/Eval.h b/src/Eval.h index 190d357b1..3203e8a15 100644 --- a/src/Eval.h +++ b/src/Eval.h @@ -27,50 +27,51 @@ #ifndef INCLUDED_EVAL #define INCLUDED_EVAL -#include -#include #include #include -bool domSource (const std::string&, Variant&); +#include +#include -class Eval -{ -public: - Eval (); +bool domSource(const std::string &, Variant &); - void addSource (bool (*fn)(const std::string&, Variant&)); - void evaluateInfixExpression (const std::string&, Variant&) const; - void evaluatePostfixExpression (const std::string&, Variant&) const; - void compileExpression (const std::vector >&); - void evaluateCompiledExpression (Variant&); - void debug (bool); +class Eval { + public: + Eval(); - static std::vector getOperators (); - static std::vector getBinaryOperators (); + void addSource(bool (*fn)(const std::string &, Variant &)); + void evaluateInfixExpression(const std::string &, Variant &) const; + void evaluatePostfixExpression(const std::string &, Variant &) const; + void compileExpression(const std::vector> &); + void evaluateCompiledExpression(Variant &); + void debug(bool); -private: - void evaluatePostfixStack (const std::vector >&, Variant&) const; - void infixToPostfix (std::vector >&) const; - void infixParse (std::vector >&) const; - bool parseLogical (std::vector >&, unsigned int &) const; - bool parseRegex (std::vector >&, unsigned int &) const; - bool parseEquality (std::vector >&, unsigned int &) const; - bool parseComparative (std::vector >&, unsigned int &) const; - bool parseArithmetic (std::vector >&, unsigned int &) const; - bool parseGeometric (std::vector >&, unsigned int &) const; - bool parseTag (std::vector >&, unsigned int &) const; - bool parseUnary (std::vector >&, unsigned int &) const; - bool parseExponent (std::vector >&, unsigned int &) const; - bool parsePrimitive (std::vector >&, unsigned int &) const; - bool identifyOperator (const std::string&, char&, unsigned int&, char&) const; + static std::vector getOperators(); + static std::vector getBinaryOperators(); - std::string dump (std::vector >&) const; + private: + void evaluatePostfixStack(const std::vector> &, + Variant &) const; + void infixToPostfix(std::vector> &) const; + void infixParse(std::vector> &) const; + bool parseLogical(std::vector> &, unsigned int &) const; + bool parseRegex(std::vector> &, unsigned int &) const; + bool parseEquality(std::vector> &, unsigned int &) const; + bool parseComparative(std::vector> &, unsigned int &) const; + bool parseArithmetic(std::vector> &, unsigned int &) const; + bool parseGeometric(std::vector> &, unsigned int &) const; + bool parseTag(std::vector> &, unsigned int &) const; + bool parseUnary(std::vector> &, unsigned int &) const; + bool parseExponent(std::vector> &, unsigned int &) const; + bool parsePrimitive(std::vector> &, unsigned int &) const; + bool identifyOperator(const std::string &, char &, unsigned int &, char &) const; -private: - std::vector _sources {}; - bool _debug {false}; - std::vector > _compiled {}; + std::string dump(std::vector> &) const; + + private: + std::vector _sources{}; + bool _debug{false}; + std::vector> _compiled{}; }; #endif diff --git a/src/Filter.cpp b/src/Filter.cpp index 4c900e917..2e6d20a00 100644 --- a/src/Filter.cpp +++ b/src/Filter.cpp @@ -25,145 +25,130 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include +// cmake.h include header must come first + #include -#include #include #include +#include +#include #include #include #include //////////////////////////////////////////////////////////////////////////////// // Take an input set of tasks and filter into a subset. -void Filter::subset (const std::vector & input, std::vector & output) -{ +void Filter::subset(const std::vector& input, std::vector& output) { Timer timer; - _startCount = (int) input.size (); + _startCount = (int)input.size(); - Context::getContext ().cli2.prepareFilter (); + Context::getContext().cli2.prepareFilter(); - std::vector > precompiled; - for (auto& a : Context::getContext ().cli2._args) - if (a.hasTag ("FILTER")) - precompiled.emplace_back (a.getToken (), a._lextype); + std::vector> precompiled; + for (auto& a : Context::getContext().cli2._args) + if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype); - if (precompiled.size ()) - { + if (precompiled.size()) { Eval eval; - eval.addSource (domSource); + eval.addSource(domSource); // Debug output from Eval during compilation is useful. During evaluation // it is mostly noise. - eval.debug (Context::getContext ().config.getInteger ("debug.parser") >= 3 ? true : false); - eval.compileExpression (precompiled); + eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false); + eval.compileExpression(precompiled); - for (auto& task : input) - { + for (auto& task : input) { // Set up context for any DOM references. - auto currentTask = Context::getContext ().withCurrentTask(&task); + auto currentTask = Context::getContext().withCurrentTask(&task); Variant var; - eval.evaluateCompiledExpression (var); - if (var.get_bool ()) - output.push_back (task); + eval.evaluateCompiledExpression(var); + if (var.get_bool()) output.push_back(task); } - eval.debug (false); - } - else + eval.debug(false); + } else output = input; - _endCount = (int) output.size (); - Context::getContext ().debug (format ("Filtered {1} tasks --> {2} tasks [list subset]", _startCount, _endCount)); - Context::getContext ().time_filter_us += timer.total_us (); + _endCount = (int)output.size(); + Context::getContext().debug( + format("Filtered {1} tasks --> {2} tasks [list subset]", _startCount, _endCount)); + Context::getContext().time_filter_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// // Take the set of all tasks and filter into a subset. -void Filter::subset (std::vector & output) -{ +void Filter::subset(std::vector& output) { Timer timer; - Context::getContext ().cli2.prepareFilter (); + Context::getContext().cli2.prepareFilter(); - std::vector > precompiled; - for (auto& a : Context::getContext ().cli2._args) - if (a.hasTag ("FILTER")) - precompiled.emplace_back (a.getToken (), a._lextype); + std::vector> precompiled; + for (auto& a : Context::getContext().cli2._args) + if (a.hasTag("FILTER")) precompiled.emplace_back(a.getToken(), a._lextype); // Shortcut indicates that only pending.data needs to be loaded. bool shortcut = false; - if (precompiled.size ()) - { + if (precompiled.size()) { Timer timer_pending; - auto pending = Context::getContext ().tdb2.pending_tasks (); - Context::getContext ().time_filter_us -= timer_pending.total_us (); - _startCount = (int) pending.size (); + auto pending = Context::getContext().tdb2.pending_tasks(); + Context::getContext().time_filter_us -= timer_pending.total_us(); + _startCount = (int)pending.size(); Eval eval; - eval.addSource (domSource); + eval.addSource(domSource); // Debug output from Eval during compilation is useful. During evaluation // it is mostly noise. - eval.debug (Context::getContext ().config.getInteger ("debug.parser") >= 3 ? true : false); - eval.compileExpression (precompiled); + eval.debug(Context::getContext().config.getInteger("debug.parser") >= 3 ? true : false); + eval.compileExpression(precompiled); - output.clear (); - for (auto& task : pending) - { + output.clear(); + for (auto& task : pending) { // Set up context for any DOM references. - auto currentTask = Context::getContext ().withCurrentTask(&task); + auto currentTask = Context::getContext().withCurrentTask(&task); Variant var; - eval.evaluateCompiledExpression (var); - if (var.get_bool ()) - output.push_back (task); + eval.evaluateCompiledExpression(var); + if (var.get_bool()) output.push_back(task); } - shortcut = pendingOnly (); - if (! shortcut) - { + shortcut = pendingOnly(); + if (!shortcut) { Timer timer_completed; - auto completed = Context::getContext ().tdb2.completed_tasks (); - Context::getContext ().time_filter_us -= timer_completed.total_us (); - _startCount += (int) completed.size (); + auto completed = Context::getContext().tdb2.completed_tasks(); + Context::getContext().time_filter_us -= timer_completed.total_us(); + _startCount += (int)completed.size(); - for (auto& task : completed) - { + for (auto& task : completed) { // Set up context for any DOM references. - auto currentTask = Context::getContext ().withCurrentTask(&task); + auto currentTask = Context::getContext().withCurrentTask(&task); Variant var; - eval.evaluateCompiledExpression (var); - if (var.get_bool ()) - output.push_back (task); + eval.evaluateCompiledExpression(var); + if (var.get_bool()) output.push_back(task); } } - eval.debug (false); - } - else - { - safety (); + eval.debug(false); + } else { + safety(); Timer pending_completed; - output = Context::getContext ().tdb2.all_tasks (); - Context::getContext ().time_filter_us -= pending_completed.total_us (); + output = Context::getContext().tdb2.all_tasks(); + Context::getContext().time_filter_us -= pending_completed.total_us(); } - _endCount = (int) output.size (); - Context::getContext ().debug (format ("Filtered {1} tasks --> {2} tasks [{3}]", _startCount, _endCount, (shortcut ? "pending only" : "all tasks"))); - Context::getContext ().time_filter_us += timer.total_us (); + _endCount = (int)output.size(); + Context::getContext().debug(format("Filtered {1} tasks --> {2} tasks [{3}]", _startCount, + _endCount, (shortcut ? "pending only" : "all tasks"))); + Context::getContext().time_filter_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// -bool Filter::hasFilter () const -{ - for (const auto& a : Context::getContext ().cli2._args) - if (a.hasTag ("FILTER")) - return true; +bool Filter::hasFilter() const { + for (const auto& a : Context::getContext().cli2._args) + if (a.hasTag("FILTER")) return true; return false; } @@ -172,11 +157,9 @@ bool Filter::hasFilter () const // If the filter contains no 'or', 'xor' or 'not' operators, and only includes // status values 'pending', 'waiting' or 'recurring', then the filter is // guaranteed to only need data from pending.data. -bool Filter::pendingOnly () const -{ +bool Filter::pendingOnly() const { // When GC is off, there are no shortcuts. - if (! Context::getContext ().config.getBoolean ("gc")) - return false; + if (!Context::getContext().config.getBoolean("gc")) return false; // To skip loading completed.data, there should be: // - 'status' in filter @@ -184,61 +167,51 @@ bool Filter::pendingOnly () const // - no 'deleted' // - no 'xor' // - no 'or' - int countStatus = 0; - int countPending = 0; - int countWaiting = 0; + int countStatus = 0; + int countPending = 0; + int countWaiting = 0; int countRecurring = 0; - int countId = (int) Context::getContext ().cli2._id_ranges.size (); - int countUUID = (int) Context::getContext ().cli2._uuid_list.size (); - int countOr = 0; - int countXor = 0; - int countNot = 0; + int countId = (int)Context::getContext().cli2._id_ranges.size(); + int countUUID = (int)Context::getContext().cli2._uuid_list.size(); + int countOr = 0; + int countXor = 0; + int countNot = 0; bool pendingTag = false; - bool activeTag = false; + bool activeTag = false; - for (const auto& a : Context::getContext ().cli2._args) - { - if (a.hasTag ("FILTER")) - { - std::string raw = a.attribute ("raw"); - std::string canonical = a.attribute ("canonical"); + for (const auto& a : Context::getContext().cli2._args) { + if (a.hasTag("FILTER")) { + std::string raw = a.attribute("raw"); + std::string canonical = a.attribute("canonical"); - if (a._lextype == Lexer::Type::op && raw == "or") ++countOr; - if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor; - if (a._lextype == Lexer::Type::op && raw == "not") ++countNot; + if (a._lextype == Lexer::Type::op && raw == "or") ++countOr; + if (a._lextype == Lexer::Type::op && raw == "xor") ++countXor; + if (a._lextype == Lexer::Type::op && raw == "not") ++countNot; if (a._lextype == Lexer::Type::dom && canonical == "status") ++countStatus; - if ( raw == "pending") ++countPending; - if ( raw == "waiting") ++countWaiting; - if ( raw == "recurring") ++countRecurring; + if (raw == "pending") ++countPending; + if (raw == "waiting") ++countWaiting; + if (raw == "recurring") ++countRecurring; } } - for (const auto& word : Context::getContext ().cli2._original_args) - { - if (word.attribute ("raw") == "+PENDING") pendingTag = true; - if (word.attribute ("raw") == "+ACTIVE") activeTag = true; + for (const auto& word : Context::getContext().cli2._original_args) { + if (word.attribute("raw") == "+PENDING") pendingTag = true; + if (word.attribute("raw") == "+ACTIVE") activeTag = true; } + if (countUUID) return false; - if (countUUID) - return false; + if (countOr || countXor || countNot) return false; - if (countOr || countXor || countNot) - return false; + if (pendingTag || activeTag) return true; - if (pendingTag || activeTag) - return true; - - if (countStatus) - { - if (!countPending && !countWaiting && !countRecurring) - return false; + if (countStatus) { + if (!countPending && !countWaiting && !countRecurring) return false; return true; } - if (countId) - return true; + if (countId) return true; return false; } @@ -246,43 +219,35 @@ bool Filter::pendingOnly () const //////////////////////////////////////////////////////////////////////////////// // Disaster avoidance mechanism. If a !READONLY has no filter, then it can cause // all tasks to be modified. This is usually not intended. -void Filter::safety () const -{ - if (_safety) - { +void Filter::safety() const { + if (_safety) { bool readonly = true; bool filter = false; - for (const auto& a : Context::getContext ().cli2._args) - { - if (a.hasTag ("CMD") && - ! a.hasTag ("READONLY")) - readonly = false; + for (const auto& a : Context::getContext().cli2._args) { + if (a.hasTag("CMD") && !a.hasTag("READONLY")) readonly = false; - if (a.hasTag ("FILTER")) - filter = true; + if (a.hasTag("FILTER")) filter = true; } - if (! readonly && - ! filter) - { - if (! Context::getContext ().config.getBoolean ("allow.empty.filter")) - throw std::string ("You did not specify a filter, and with the 'allow.empty.filter' value, no action is taken."); + if (!readonly && !filter) { + if (!Context::getContext().config.getBoolean("allow.empty.filter")) + throw std::string( + "You did not specify a filter, and with the 'allow.empty.filter' value, no action is " + "taken."); // If user is willing to be asked, this can be avoided. - if (Context::getContext ().config.getBoolean ("confirmation") && - confirm ("This command has no filter, and will modify all (including completed and deleted) tasks. Are you sure?")) + if (Context::getContext().config.getBoolean("confirmation") && + confirm("This command has no filter, and will modify all (including completed and " + "deleted) tasks. Are you sure?")) return; // Sound the alarm. - throw std::string ("Command prevented from running."); + throw std::string("Command prevented from running."); } } } //////////////////////////////////////////////////////////////////////////////// -void Filter::disableSafety () -{ - _safety = false; -} +void Filter::disableSafety() { _safety = false; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Filter.h b/src/Filter.h index da15c68b3..bb7449318 100644 --- a/src/Filter.h +++ b/src/Filter.h @@ -27,28 +27,26 @@ #ifndef INCLUDED_FILTER #define INCLUDED_FILTER -#include -#include #include #include -class Filter -{ -public: - Filter () = default; +#include - void subset (const std::vector &, std::vector &); - void subset (std::vector &); - bool hasFilter () const; - bool pendingOnly () const; - void safety () const; - void disableSafety (); +class Filter { + public: + Filter() = default; -private: - int _startCount {0}; - int _endCount {0}; - bool _safety {true}; + void subset(const std::vector&, std::vector&); + void subset(std::vector&); + bool hasFilter() const; + bool pendingOnly() const; + void safety() const; + void disableSafety(); + + private: + int _startCount{0}; + int _endCount{0}; + bool _safety{true}; }; #endif - diff --git a/src/Hooks.cpp b/src/Hooks.cpp index 12c8d1756..1d1e440a4 100644 --- a/src/Hooks.cpp +++ b/src/Hooks.cpp @@ -25,91 +25,85 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include + #include // If is included, put it after , because it includes // , and therefore would ignore the _WITH_GETLINE. #ifdef FREEBSD #define _WITH_GETLINE #endif -#include -#include -#include -#include #include -#include #include -#include -#include -#include #include +#include +#include +#include +#include #include #include +#include +#include +#include #include -#define STRING_HOOK_ERROR_OBJECT "Hook Error: JSON Object '{...}' expected from hook script: {1}" -#define STRING_HOOK_ERROR_NODESC "Hook Error: JSON Object missing 'description' attribute from hook script: {1}" -#define STRING_HOOK_ERROR_NOUUID "Hook Error: JSON Object missing 'uuid' attribute from hook script: {1}" -#define STRING_HOOK_ERROR_SYNTAX "Hook Error: JSON syntax error in: {1}" -#define STRING_HOOK_ERROR_JSON "Hook Error: JSON " -#define STRING_HOOK_ERROR_NOPARSE "Hook Error: JSON failed to parse: " -#define STRING_HOOK_ERROR_BAD_NUM "Hook Error: Expected {1} JSON task(s), found {2}, in hook script: {3}" -#define STRING_HOOK_ERROR_SAME1 "Hook Error: JSON must be for the same task: {1}, in hook script: {2}" -#define STRING_HOOK_ERROR_SAME2 "Hook Error: JSON must be for the same task: {1} != {2}, in hook script: {3}" +#define STRING_HOOK_ERROR_OBJECT "Hook Error: JSON Object '{...}' expected from hook script: {1}" +#define STRING_HOOK_ERROR_NODESC \ + "Hook Error: JSON Object missing 'description' attribute from hook script: {1}" +#define STRING_HOOK_ERROR_NOUUID \ + "Hook Error: JSON Object missing 'uuid' attribute from hook script: {1}" +#define STRING_HOOK_ERROR_SYNTAX "Hook Error: JSON syntax error in: {1}" +#define STRING_HOOK_ERROR_JSON "Hook Error: JSON " +#define STRING_HOOK_ERROR_NOPARSE "Hook Error: JSON failed to parse: " +#define STRING_HOOK_ERROR_BAD_NUM \ + "Hook Error: Expected {1} JSON task(s), found {2}, in hook script: {3}" +#define STRING_HOOK_ERROR_SAME1 \ + "Hook Error: JSON must be for the same task: {1}, in hook script: {2}" +#define STRING_HOOK_ERROR_SAME2 \ + "Hook Error: JSON must be for the same task: {1} != {2}, in hook script: {3}" #define STRING_HOOK_ERROR_NOFEEDBACK "Hook Error: Expected feedback from failing hook script: {1}" //////////////////////////////////////////////////////////////////////////////// -void Hooks::initialize () -{ - _debug = Context::getContext ().config.getInteger ("debug.hooks"); +void Hooks::initialize() { + _debug = Context::getContext().config.getInteger("debug.hooks"); // Scan // /hooks Directory d; - if (Context::getContext ().config.has ("hooks.location")) - { - d = Directory (Context::getContext ().config.get ("hooks.location")); - } - else - { - d = Directory (Context::getContext ().config.get ("data.location")); + if (Context::getContext().config.has("hooks.location")) { + d = Directory(Context::getContext().config.get("hooks.location")); + } else { + d = Directory(Context::getContext().config.get("data.location")); d += "hooks"; } - if (d.is_directory () && - d.readable ()) - { - _scripts = d.list (); - std::sort (_scripts.begin (), _scripts.end ()); + if (d.is_directory() && d.readable()) { + _scripts = d.list(); + std::sort(_scripts.begin(), _scripts.end()); - if (_debug >= 1) - { - for (auto& i : _scripts) - { - Path p (i); - if (! p.is_directory ()) - { - std::string name = p.name (); - if (name.substr (0, 6) == "on-add" || - name.substr (0, 9) == "on-modify" || - name.substr (0, 9) == "on-launch" || - name.substr (0, 7) == "on-exit") - Context::getContext ().debug ("Found hook script " + i); + if (_debug >= 1) { + for (auto& i : _scripts) { + Path p(i); + if (!p.is_directory()) { + std::string name = p.name(); + if (name.substr(0, 6) == "on-add" || name.substr(0, 9) == "on-modify" || + name.substr(0, 9) == "on-launch" || name.substr(0, 7) == "on-exit") + Context::getContext().debug("Found hook script " + i); else - Context::getContext ().debug ("Found misnamed hook script " + i); + Context::getContext().debug("Found misnamed hook script " + i); } } } - } - else if (_debug >= 1) - Context::getContext ().debug ("Hook directory not readable: " + d._data); + } else if (_debug >= 1) + Context::getContext().debug("Hook directory not readable: " + d._data); - _enabled = Context::getContext ().config.getBoolean ("hooks"); + _enabled = Context::getContext().config.getBoolean("hooks"); } //////////////////////////////////////////////////////////////////////////////// -bool Hooks::enable (bool value) -{ +bool Hooks::enable(bool value) { bool old_value = _enabled; _enabled = value; return old_value; @@ -127,45 +121,36 @@ bool Hooks::enable (bool value) // - all emitted non-JSON lines are considered feedback or error messages // depending on the status code. // -void Hooks::onLaunch () const -{ - if (! _enabled) - return; +void Hooks::onLaunch() const { + if (!_enabled) return; Timer timer; - std::vector matchingScripts = scripts ("on-launch"); - if (matchingScripts.size ()) - { - for (auto& script : matchingScripts) - { - std::vector input; - std::vector output; - int status = callHookScript (script, input, output); + std::vector matchingScripts = scripts("on-launch"); + if (matchingScripts.size()) { + for (auto& script : matchingScripts) { + std::vector input; + std::vector output; + int status = callHookScript(script, input, output); - std::vector outputJSON; - std::vector outputFeedback; - separateOutput (output, outputJSON, outputFeedback); + std::vector outputJSON; + std::vector outputFeedback; + separateOutput(output, outputJSON, outputFeedback); - assertNTasks (outputJSON, 0, script); + assertNTasks(outputJSON, 0, script); - if (status == 0) - { - for (auto& message : outputFeedback) - Context::getContext ().footnote (message); - } - else - { - assertFeedback (outputFeedback, script); - for (auto& message : outputFeedback) - Context::getContext ().error (message); + if (status == 0) { + for (auto& message : outputFeedback) Context::getContext().footnote(message); + } else { + assertFeedback(outputFeedback, script); + for (auto& message : outputFeedback) Context::getContext().error(message); throw 0; // This is how hooks silently terminate processing. } } } - Context::getContext ().time_hooks_us += timer.total_us (); + Context::getContext().time_hooks_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// @@ -180,55 +165,45 @@ void Hooks::onLaunch () const // - all emitted non-JSON lines are considered feedback or error messages // depending on the status code. // -void Hooks::onExit () const -{ - if (! _enabled) - return; +void Hooks::onExit() const { + if (!_enabled) return; Timer timer; - std::vector matchingScripts = scripts ("on-exit"); - if (matchingScripts.size ()) - { + std::vector matchingScripts = scripts("on-exit"); + if (matchingScripts.size()) { // Get the set of changed tasks. - std::vector tasks; - Context::getContext ().tdb2.get_changes (tasks); + std::vector tasks; + Context::getContext().tdb2.get_changes(tasks); // Convert to a vector of strings. - std::vector input; + std::vector input; input.reserve(tasks.size()); - for (auto& t : tasks) - input.push_back (t.composeJSON ()); + for (auto& t : tasks) input.push_back(t.composeJSON()); // Call the hook scripts, with the invariant input. - for (auto& script : matchingScripts) - { - std::vector output; - int status = callHookScript (script, input, output); + for (auto& script : matchingScripts) { + std::vector output; + int status = callHookScript(script, input, output); - std::vector outputJSON; - std::vector outputFeedback; - separateOutput (output, outputJSON, outputFeedback); + std::vector outputJSON; + std::vector outputFeedback; + separateOutput(output, outputJSON, outputFeedback); - assertNTasks (outputJSON, 0, script); + assertNTasks(outputJSON, 0, script); - if (status == 0) - { - for (auto& message : outputFeedback) - Context::getContext ().footnote (message); - } - else - { - assertFeedback (outputFeedback, script); - for (auto& message : outputFeedback) - Context::getContext ().error (message); + if (status == 0) { + for (auto& message : outputFeedback) Context::getContext().footnote(message); + } else { + assertFeedback(outputFeedback, script); + for (auto& message : outputFeedback) Context::getContext().error(message); throw 0; // This is how hooks silently terminate processing. } } } - Context::getContext ().time_hooks_us += timer.total_us (); + Context::getContext().time_hooks_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// @@ -243,57 +218,48 @@ void Hooks::onExit () const // - all emitted non-JSON lines are considered feedback or error messages // depending on the status code. // -void Hooks::onAdd (Task& task) const -{ - if (! _enabled) - return; +void Hooks::onAdd(Task& task) const { + if (!_enabled) return; Timer timer; - std::vector matchingScripts = scripts ("on-add"); - if (matchingScripts.size ()) - { + std::vector matchingScripts = scripts("on-add"); + if (matchingScripts.size()) { // Convert task to a vector of strings. - std::vector input; - input.push_back (task.composeJSON ()); + std::vector input; + input.push_back(task.composeJSON()); // Call the hook scripts. - for (auto& script : matchingScripts) - { - std::vector output; - int status = callHookScript (script, input, output); + for (auto& script : matchingScripts) { + std::vector output; + int status = callHookScript(script, input, output); - std::vector outputJSON; - std::vector outputFeedback; - separateOutput (output, outputJSON, outputFeedback); + std::vector outputJSON; + std::vector outputFeedback; + separateOutput(output, outputJSON, outputFeedback); - if (status == 0) - { - assertNTasks (outputJSON, 1, script); - assertValidJSON (outputJSON, script); - assertSameTask (outputJSON, task, script); + if (status == 0) { + assertNTasks(outputJSON, 1, script); + assertValidJSON(outputJSON, script); + assertSameTask(outputJSON, task, script); // Propagate forward to the next script. input[0] = outputJSON[0]; - for (auto& message : outputFeedback) - Context::getContext ().footnote (message); - } - else - { - assertFeedback (outputFeedback, script); - for (auto& message : outputFeedback) - Context::getContext ().error (message); + for (auto& message : outputFeedback) Context::getContext().footnote(message); + } else { + assertFeedback(outputFeedback, script); + for (auto& message : outputFeedback) Context::getContext().error(message); throw 0; // This is how hooks silently terminate processing. } } // Transfer the modified task back to the original task. - task = Task (input[0]); + task = Task(input[0]); } - Context::getContext ().time_hooks_us += timer.total_us (); + Context::getContext().time_hooks_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// @@ -309,76 +275,60 @@ void Hooks::onAdd (Task& task) const // - all emitted non-JSON lines are considered feedback or error messages // depending on the status code. // -void Hooks::onModify (const Task& before, Task& after) const -{ - if (! _enabled) - return; +void Hooks::onModify(Task& before, Task& after) const { + if (!_enabled) return; Timer timer; - std::vector matchingScripts = scripts ("on-modify"); - if (matchingScripts.size ()) - { + std::vector matchingScripts = scripts("on-modify"); + if (matchingScripts.size()) { // Convert vector of tasks to a vector of strings. - std::vector input; - input.push_back (before.composeJSON ()); // [line 0] original, never changes - input.push_back (after.composeJSON ()); // [line 1] modified + std::vector input; + input.push_back(before.composeJSON()); // [line 0] original, never changes + input.push_back(after.composeJSON()); // [line 1] modified // Call the hook scripts. - for (auto& script : matchingScripts) - { - std::vector output; - int status = callHookScript (script, input, output); + for (auto& script : matchingScripts) { + std::vector output; + int status = callHookScript(script, input, output); - std::vector outputJSON; - std::vector outputFeedback; - separateOutput (output, outputJSON, outputFeedback); + std::vector outputJSON; + std::vector outputFeedback; + separateOutput(output, outputJSON, outputFeedback); - if (status == 0) - { - assertNTasks (outputJSON, 1, script); - assertValidJSON (outputJSON, script); - assertSameTask (outputJSON, before, script); + if (status == 0) { + assertNTasks(outputJSON, 1, script); + assertValidJSON(outputJSON, script); + assertSameTask(outputJSON, before, script); // Propagate accepted changes forward to the next script. input[1] = outputJSON[0]; - for (auto& message : outputFeedback) - Context::getContext ().footnote (message); - } - else - { - assertFeedback (outputFeedback, script); - for (auto& message : outputFeedback) - Context::getContext ().error (message); + for (auto& message : outputFeedback) Context::getContext().footnote(message); + } else { + assertFeedback(outputFeedback, script); + for (auto& message : outputFeedback) Context::getContext().error(message); throw 0; // This is how hooks silently terminate processing. } } - after = Task (input[1]); + after = Task(input[1]); } - Context::getContext ().time_hooks_us += timer.total_us (); + Context::getContext().time_hooks_us += timer.total_us(); } //////////////////////////////////////////////////////////////////////////////// -std::vector Hooks::list () const -{ - return _scripts; -} +std::vector Hooks::list() const { return _scripts; } //////////////////////////////////////////////////////////////////////////////// -std::vector Hooks::scripts (const std::string& event) const -{ - std::vector matching; - for (const auto& i : _scripts) - { - if (i.find ("/" + event) != std::string::npos) - { - File script (i); - if (script.executable ()) - matching.push_back (i); +std::vector Hooks::scripts(const std::string& event) const { + std::vector matching; + for (const auto& i : _scripts) { + if (i.find("/" + event) != std::string::npos) { + File script(i); + if (script.executable()) matching.push_back(i); } } @@ -386,124 +336,95 @@ std::vector Hooks::scripts (const std::string& event) const } //////////////////////////////////////////////////////////////////////////////// -void Hooks::separateOutput ( - const std::vector & output, - std::vector & json, - std::vector & feedback) const -{ - for (auto& i : output) - { - if (isJSON (i)) - json.push_back (i); +void Hooks::separateOutput(const std::vector& output, std::vector& json, + std::vector& feedback) const { + for (auto& i : output) { + if (isJSON(i)) + json.push_back(i); else - feedback.push_back (i); + feedback.push_back(i); } } //////////////////////////////////////////////////////////////////////////////// -bool Hooks::isJSON (const std::string& input) const -{ - return input.length () > 2 && - input[0] == '{' && - input[input.length () - 1] == '}'; +bool Hooks::isJSON(const std::string& input) const { + return input.length() > 2 && input[0] == '{' && input[input.length() - 1] == '}'; } //////////////////////////////////////////////////////////////////////////////// -void Hooks::assertValidJSON ( - const std::vector & input, - const std::string& script) const -{ - for (auto& i : input) - { - if (i.length () < 3 || - i[0] != '{' || - i[i.length () - 1] != '}') - { - Context::getContext ().error (format (STRING_HOOK_ERROR_OBJECT, Path (script).name ())); +void Hooks::assertValidJSON(const std::vector& input, + const std::string& script) const { + for (auto& i : input) { + if (i.length() < 3 || i[0] != '{' || i[i.length() - 1] != '}') { + Context::getContext().error(format(STRING_HOOK_ERROR_OBJECT, Path(script).name())); throw 0; } - try - { - json::value* root = json::parse (i); - if (root->type () != json::j_object) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_OBJECT, Path (script).name ())); + try { + json::value* root = json::parse(i); + if (root->type() != json::j_object) { + Context::getContext().error(format(STRING_HOOK_ERROR_OBJECT, Path(script).name())); throw 0; } - if (((json::object*)root)->_data.find ("description") == ((json::object*)root)->_data.end ()) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_NODESC, Path (script).name ())); + if (((json::object*)root)->_data.find("description") == ((json::object*)root)->_data.end()) { + Context::getContext().error(format(STRING_HOOK_ERROR_NODESC, Path(script).name())); throw 0; } - if (((json::object*)root)->_data.find ("uuid") == ((json::object*)root)->_data.end ()) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_NOUUID, Path (script).name ())); + if (((json::object*)root)->_data.find("uuid") == ((json::object*)root)->_data.end()) { + Context::getContext().error(format(STRING_HOOK_ERROR_NOUUID, Path(script).name())); throw 0; } delete root; } - catch (const std::string& e) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_SYNTAX, i)); - if (_debug) - Context::getContext ().error (STRING_HOOK_ERROR_JSON + e); + catch (const std::string& e) { + Context::getContext().error(format(STRING_HOOK_ERROR_SYNTAX, i)); + if (_debug) Context::getContext().error(STRING_HOOK_ERROR_JSON + e); throw 0; } - catch (...) - { - Context::getContext ().error (STRING_HOOK_ERROR_NOPARSE + i); + catch (...) { + Context::getContext().error(STRING_HOOK_ERROR_NOPARSE + i); throw 0; } } } //////////////////////////////////////////////////////////////////////////////// -void Hooks::assertNTasks ( - const std::vector & input, - unsigned int n, - const std::string& script) const -{ - if (input.size () != n) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_BAD_NUM, n, (int) input.size (), Path (script).name ())); +void Hooks::assertNTasks(const std::vector& input, unsigned int n, + const std::string& script) const { + if (input.size() != n) { + Context::getContext().error( + format(STRING_HOOK_ERROR_BAD_NUM, n, (int)input.size(), Path(script).name())); throw 0; } } //////////////////////////////////////////////////////////////////////////////// -void Hooks::assertSameTask ( - const std::vector & input, - const Task& task, - const std::string& script) const -{ - std::string uuid = task.get ("uuid"); +void Hooks::assertSameTask(const std::vector& input, const Task& task, + const std::string& script) const { + std::string uuid = task.get("uuid"); - for (auto& i : input) - { - auto root_obj = (json::object*)json::parse (i); + for (auto& i : input) { + auto root_obj = (json::object*)json::parse(i); // If there is no UUID at all. - auto u = root_obj->_data.find ("uuid"); - if (u == root_obj->_data.end () || - u->second->type () != json::j_string) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_SAME1, uuid, Path (script).name ())); + auto u = root_obj->_data.find("uuid"); + if (u == root_obj->_data.end() || u->second->type() != json::j_string) { + Context::getContext().error(format(STRING_HOOK_ERROR_SAME1, uuid, Path(script).name())); throw 0; } - auto up = (json::string*) u->second; - auto text = up->dump (); - Lexer::dequote (text); - std::string json_uuid = json::decode (text); - if (json_uuid != uuid) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_SAME2, uuid, json_uuid, Path (script).name ())); + auto up = (json::string*)u->second; + auto text = up->dump(); + Lexer::dequote(text); + std::string json_uuid = json::decode(text); + if (json_uuid != uuid) { + Context::getContext().error( + format(STRING_HOOK_ERROR_SAME2, uuid, json_uuid, Path(script).name())); throw 0; } @@ -512,101 +433,82 @@ void Hooks::assertSameTask ( } //////////////////////////////////////////////////////////////////////////////// -void Hooks::assertFeedback ( - const std::vector & input, - const std::string& script) const -{ +void Hooks::assertFeedback(const std::vector& input, const std::string& script) const { bool foundSomething = false; for (auto& i : input) - if (nontrivial (i)) - foundSomething = true; + if (nontrivial(i)) foundSomething = true; - if (! foundSomething) - { - Context::getContext ().error (format (STRING_HOOK_ERROR_NOFEEDBACK, Path (script).name ())); + if (!foundSomething) { + Context::getContext().error(format(STRING_HOOK_ERROR_NOFEEDBACK, Path(script).name())); throw 0; } } //////////////////////////////////////////////////////////////////////////////// -std::vector & Hooks::buildHookScriptArgs (std::vector & args) const -{ +std::vector& Hooks::buildHookScriptArgs(std::vector& args) const { Variant v; // Hooks API version. - args.push_back ("api:2"); + args.push_back("api:2"); // Command line Taskwarrior was called with. - getDOM ("context.args", v); - args.push_back ("args:" + std::string (v)); + getDOM("context.args", v); + args.push_back("args:" + std::string(v)); // Command to be executed. - args.push_back ("command:" + Context::getContext ().cli2.getCommand ()); + args.push_back("command:" + Context::getContext().cli2.getCommand()); // rc file used after applying all overrides. - args.push_back ("rc:" + Context::getContext ().rc_file._data); + args.push_back("rc:" + Context::getContext().rc_file._data); // Directory containing *.data files. - args.push_back ("data:" + Context::getContext ().data_dir._data); + args.push_back("data:" + Context::getContext().data_dir._data); // Taskwarrior version, same as returned by "task --version" - args.push_back ("version:" + std::string(VERSION)); + args.push_back("version:" + std::string(VERSION)); return args; } //////////////////////////////////////////////////////////////////////////////// -int Hooks::callHookScript ( - const std::string& script, - const std::vector & input, - std::vector & output) const -{ - if (_debug >= 1) - Context::getContext ().debug ("Hook: Calling " + script); +int Hooks::callHookScript(const std::string& script, const std::vector& input, + std::vector& output) const { + if (_debug >= 1) Context::getContext().debug("Hook: Calling " + script); - if (_debug >= 2) - { - Context::getContext ().debug ("Hook: input"); - for (const auto& i : input) - Context::getContext ().debug (" " + i); + if (_debug >= 2) { + Context::getContext().debug("Hook: input"); + for (const auto& i : input) Context::getContext().debug(" " + i); } std::string inputStr; - for (const auto& i : input) - inputStr += i + "\n"; + for (const auto& i : input) inputStr += i + "\n"; - std::vector args; - buildHookScriptArgs (args); - if (_debug >= 2) - { - Context::getContext ().debug ("Hooks: args"); - for (const auto& arg: args) - Context::getContext ().debug (" " + arg); + std::vector args; + buildHookScriptArgs(args); + if (_debug >= 2) { + Context::getContext().debug("Hooks: args"); + for (const auto& arg : args) Context::getContext().debug(" " + arg); } // Measure time for each hook if running in debug int status; std::string outputStr; - if (_debug >= 2) - { + if (_debug >= 2) { Timer timer; - status = execute (script, args, inputStr, outputStr); - Context::getContext ().debugTiming (format ("Hooks::execute ({1})", script), timer); - } - else - status = execute (script, args, inputStr, outputStr); + status = execute(script, args, inputStr, outputStr); + Context::getContext().debugTiming(format("Hooks::execute ({1})", script), timer); + } else + status = execute(script, args, inputStr, outputStr); - output = split (outputStr, '\n'); + output = split(outputStr, '\n'); - if (_debug >= 2) - { - Context::getContext ().debug ("Hook: output"); + if (_debug >= 2) { + Context::getContext().debug("Hook: output"); for (const auto& i : output) - if (i != "") - Context::getContext ().debug (" " + i); + if (i != "") Context::getContext().debug(" " + i); - Context::getContext ().debug (format ("Hook: Completed with status {1}", status)); - Context::getContext ().debug (" "); // Blank line + Context::getContext().debug(format("Hook: Completed with status {1}", status)); + Context::getContext().debug(" "); // Blank line } return status; diff --git a/src/Hooks.h b/src/Hooks.h index a65bc9ff3..448c59744 100644 --- a/src/Hooks.h +++ b/src/Hooks.h @@ -27,37 +27,39 @@ #ifndef INCLUDED_HOOKS #define INCLUDED_HOOKS -#include -#include #include -class Hooks -{ -public: - Hooks () = default; - void initialize (); - bool enable (bool); - void onLaunch () const; - void onExit () const; - void onAdd (Task&) const; - void onModify (const Task&, Task&) const; - std::vector list () const; +#include +#include -private: - std::vector scripts (const std::string&) const; - void separateOutput (const std::vector &, std::vector &, std::vector &) const; - bool isJSON (const std::string&) const; - void assertValidJSON (const std::vector &, const std::string&) const; - void assertNTasks (const std::vector &, unsigned int, const std::string&) const; - void assertSameTask (const std::vector &, const Task&, const std::string&) const; - void assertFeedback (const std::vector &, const std::string&) const; - std::vector & buildHookScriptArgs (std::vector &) const; - int callHookScript (const std::string&, const std::vector &, std::vector &) const; +class Hooks { + public: + Hooks() = default; + void initialize(); + bool enable(bool); + void onLaunch() const; + void onExit() const; + void onAdd(Task&) const; + void onModify(Task&, Task&) const; + std::vector list() const; -private: - bool _enabled {true}; - int _debug {0}; - std::vector _scripts {}; + private: + std::vector scripts(const std::string&) const; + void separateOutput(const std::vector&, std::vector&, + std::vector&) const; + bool isJSON(const std::string&) const; + void assertValidJSON(const std::vector&, const std::string&) const; + void assertNTasks(const std::vector&, unsigned int, const std::string&) const; + void assertSameTask(const std::vector&, const Task&, const std::string&) const; + void assertFeedback(const std::vector&, const std::string&) const; + std::vector& buildHookScriptArgs(std::vector&) const; + int callHookScript(const std::string&, const std::vector&, + std::vector&) const; + + private: + bool _enabled{true}; + int _debug{0}; + std::vector _scripts{}; }; #endif diff --git a/src/Lexer.cpp b/src/Lexer.cpp index 801d6b491..0c3b8a38e 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -25,42 +25,36 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include +// cmake.h include header must come first + #include #include +#include +#include #include #include +#include + static const std::string uuid_pattern = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; static const unsigned int uuid_min_length = 8; std::string Lexer::dateFormat = ""; std::string::size_type Lexer::minimumMatchLength = 3; -std::map Lexer::attributes; - +std::map Lexer::attributes; //////////////////////////////////////////////////////////////////////////////// -Lexer::Lexer (const std::string& text) -: _text (text) -, _cursor (0) -, _eos (text.size ()) -{ -} +Lexer::Lexer(const std::string& text) : _text(text), _cursor(0), _eos(text.size()) {} //////////////////////////////////////////////////////////////////////////////// // When a Lexer object is constructed with a string, this method walks through // the stream of low-level tokens. -bool Lexer::token (std::string& token, Lexer::Type& type) -{ +bool Lexer::token(std::string& token, Lexer::Type& type) { // Eat white space. - while (unicodeWhitespace (_text[_cursor])) - utf8_next_char (_text, _cursor); + while (unicodeWhitespace(_text[_cursor])) utf8_next_char(_text, _cursor); // Terminate at EOS. - if (isEOS ()) - return false; + if (isEOS()) return false; // The sequence is specific, and must follow these rules: // - date < duration < uuid < identifier @@ -72,24 +66,12 @@ bool Lexer::token (std::string& token, Lexer::Type& type) // - path < substitution < pattern // - set < number // - word last - if (isString (token, type, "'\"") || - isDate (token, type) || - isDuration (token, type) || - isURL (token, type) || - isPair (token, type) || - isUUID (token, type, true) || - isSet (token, type) || - isDOM (token, type) || - isHexNumber (token, type) || - isNumber (token, type) || - isSeparator (token, type) || - isTag (token, type) || - isPath (token, type) || - isSubstitution (token, type) || - isPattern (token, type) || - isOperator (token, type) || - isIdentifier (token, type) || - isWord (token, type)) + if (isString(token, type, "'\"") || isDate(token, type) || isDuration(token, type) || + isURL(token, type) || isPair(token, type) || isUUID(token, type, true) || + isSet(token, type) || isDOM(token, type) || isHexNumber(token, type) || + isNumber(token, type) || isSeparator(token, type) || isTag(token, type) || + isPath(token, type) || isSubstitution(token, type) || isPattern(token, type) || + isOperator(token, type) || isIdentifier(token, type) || isWord(token, type)) return true; return false; @@ -97,70 +79,78 @@ bool Lexer::token (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // This static method tokenizes the input, but discards the type information. -std::vector Lexer::split (const std::string& text) -{ - std::vector all; +std::vector Lexer::split(const std::string& text) { + std::vector all; std::string token; Lexer::Type ignored; - Lexer l (text); - while (l.token (token, ignored)) - all.push_back (token); + Lexer l(text); + while (l.token(token, ignored)) all.push_back(token); return all; } //////////////////////////////////////////////////////////////////////////////// // No L10N - these are for internal purposes. -const std::string Lexer::typeName (const Lexer::Type& type) -{ - switch (type) - { - case Lexer::Type::uuid: return "uuid"; - case Lexer::Type::number: return "number"; - case Lexer::Type::hex: return "hex"; - case Lexer::Type::string: return "string"; - case Lexer::Type::url: return "url"; - case Lexer::Type::pair: return "pair"; - case Lexer::Type::set: return "set"; - case Lexer::Type::separator: return "separator"; - case Lexer::Type::tag: return "tag"; - case Lexer::Type::path: return "path"; - case Lexer::Type::substitution: return "substitution"; - case Lexer::Type::pattern: return "pattern"; - case Lexer::Type::op: return "op"; - case Lexer::Type::dom: return "dom"; - case Lexer::Type::identifier: return "identifier"; - case Lexer::Type::word: return "word"; - case Lexer::Type::date: return "date"; - case Lexer::Type::duration: return "duration"; +const std::string Lexer::typeName(const Lexer::Type& type) { + switch (type) { + case Lexer::Type::uuid: + return "uuid"; + case Lexer::Type::number: + return "number"; + case Lexer::Type::hex: + return "hex"; + case Lexer::Type::string: + return "string"; + case Lexer::Type::url: + return "url"; + case Lexer::Type::pair: + return "pair"; + case Lexer::Type::set: + return "set"; + case Lexer::Type::separator: + return "separator"; + case Lexer::Type::tag: + return "tag"; + case Lexer::Type::path: + return "path"; + case Lexer::Type::substitution: + return "substitution"; + case Lexer::Type::pattern: + return "pattern"; + case Lexer::Type::op: + return "op"; + case Lexer::Type::dom: + return "dom"; + case Lexer::Type::identifier: + return "identifier"; + case Lexer::Type::word: + return "word"; + case Lexer::Type::date: + return "date"; + case Lexer::Type::duration: + return "duration"; } return "unknown"; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isIdentifierStart (int c) -{ - return c && // Include null character check. - ! unicodeWhitespace (c) && - ! unicodeLatinDigit (c) && - ! isSingleCharOperator (c) && - ! isPunctuation (c); +bool Lexer::isIdentifierStart(int c) { + return c && // Include null character check. + !unicodeWhitespace(c) && !unicodeLatinDigit(c) && !isSingleCharOperator(c) && + !isPunctuation(c); } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isIdentifierNext (int c) -{ - return c && // Include null character check. - c != ':' && // Used in isPair. - c != '=' && // Used in isPair. - ! unicodeWhitespace (c) && - ! isSingleCharOperator (c); +bool Lexer::isIdentifierNext(int c) { + return c && // Include null character check. + c != ':' && // Used in isPair. + c != '=' && // Used in isPair. + !unicodeWhitespace(c) && !isSingleCharOperator(c); } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isSingleCharOperator (int c) -{ +bool Lexer::isSingleCharOperator(int c) { return c == '+' || // Addition c == '-' || // Subtraction or unary minus = ambiguous c == '*' || // Multiplication @@ -177,129 +167,95 @@ bool Lexer::isSingleCharOperator (int c) } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isDoubleCharOperator (int c0, int c1, int c2) -{ - return (c0 == '=' && c1 == '=') || - (c0 == '!' && c1 == '=') || - (c0 == '<' && c1 == '=') || - (c0 == '>' && c1 == '=') || - (c0 == 'o' && c1 == 'r' && isBoundary (c1, c2)) || - (c0 == '|' && c1 == '|') || - (c0 == '&' && c1 == '&') || - (c0 == '!' && c1 == '~'); +bool Lexer::isDoubleCharOperator(int c0, int c1, int c2) { + return (c0 == '=' && c1 == '=') || (c0 == '!' && c1 == '=') || (c0 == '<' && c1 == '=') || + (c0 == '>' && c1 == '=') || (c0 == 'o' && c1 == 'r' && isBoundary(c1, c2)) || + (c0 == '|' && c1 == '|') || (c0 == '&' && c1 == '&') || (c0 == '!' && c1 == '~'); } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isTripleCharOperator (int c0, int c1, int c2, int c3) -{ - return (c0 == 'a' && c1 == 'n' && c2 == 'd' && isBoundary (c2, c3)) || - (c0 == 'x' && c1 == 'o' && c2 == 'r' && isBoundary (c2, c3)) || +bool Lexer::isTripleCharOperator(int c0, int c1, int c2, int c3) { + return (c0 == 'a' && c1 == 'n' && c2 == 'd' && isBoundary(c2, c3)) || + (c0 == 'x' && c1 == 'o' && c2 == 'r' && isBoundary(c2, c3)) || (c0 == '!' && c1 == '=' && c2 == '='); } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isBoundary (int left, int right) -{ +bool Lexer::isBoundary(int left, int right) { // EOS - if (right == '\0') return true; + if (right == '\0') return true; // XOR - if (unicodeLatinAlpha (left) != unicodeLatinAlpha (right)) return true; - if (unicodeLatinDigit (left) != unicodeLatinDigit (right)) return true; - if (unicodeWhitespace (left) != unicodeWhitespace (right)) return true; + if (unicodeLatinAlpha(left) != unicodeLatinAlpha(right)) return true; + if (unicodeLatinDigit(left) != unicodeLatinDigit(right)) return true; + if (unicodeWhitespace(left) != unicodeWhitespace(right)) return true; // OR - if (isPunctuation (left) || isPunctuation (right)) return true; + if (isPunctuation(left) || isPunctuation(right)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isHardBoundary (int left, int right) -{ +bool Lexer::isHardBoundary(int left, int right) { // EOS - if (right == '\0') - return true; + if (right == '\0') return true; // FILTER operators that don't need to be surrounded by whitespace. - if (left == '(' || - left == ')' || - right == '(' || - right == ')') - return true; + if (left == '(' || left == ')' || right == '(' || right == ')') return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isPunctuation (int c) -{ - return isprint (c) && - c != ' ' && - c != '@' && - c != '#' && - c != '$' && - c != '_' && - ! unicodeLatinDigit (c) && - ! unicodeLatinAlpha (c); +bool Lexer::isPunctuation(int c) { + return isprint(c) && c != ' ' && c != '@' && c != '#' && c != '$' && c != '_' && + !unicodeLatinDigit(c) && !unicodeLatinAlpha(c); } //////////////////////////////////////////////////////////////////////////////// // Assumes that quotes is a string containing a non-trivial set of quote // characters. -void Lexer::dequote (std::string& input, const std::string& quotes) -{ +void Lexer::dequote(std::string& input, const std::string& quotes) { int quote = input[0]; - if (quotes.find (quote) != std::string::npos) - { - size_t len = input.length (); - if (quote == input[len - 1]) - input = input.substr (1, len - 2); + if (quotes.find(quote) != std::string::npos) { + size_t len = input.length(); + if (quote == input[len - 1]) input = input.substr(1, len - 2); } } //////////////////////////////////////////////////////////////////////////////// // Detects characters in an input string that indicate quotes were required, or // escapes, to get them past the shell. -bool Lexer::wasQuoted (const std::string& input) -{ - if (input.find_first_of (" \t()<>&~") != std::string::npos) - return true; +bool Lexer::wasQuoted(const std::string& input) { + if (input.find_first_of(" \t()<>&~") != std::string::npos) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isEOS () const -{ - return _cursor >= _eos; -} +bool Lexer::isEOS() const { return _cursor >= _eos; } //////////////////////////////////////////////////////////////////////////////// // Converts '0' -> 0 // '9' -> 9 // 'a'/'A' -> 10 // 'f'/'F' -> 15 -int Lexer::hexToInt (int c) -{ - if (c >= '0' && c <= '9') return (c - '0'); - else if (c >= 'a' && c <= 'f') return (c - 'a' + 10); - else return (c - 'A' + 10); +int Lexer::hexToInt(int c) { + if (c >= '0' && c <= '9') + return (c - '0'); + else if (c >= 'a' && c <= 'f') + return (c - 'a' + 10); + else + return (c - 'A' + 10); } //////////////////////////////////////////////////////////////////////////////// -int Lexer::hexToInt (int c0, int c1) -{ - return (hexToInt (c0) << 4) + hexToInt (c1); -} +int Lexer::hexToInt(int c0, int c1) { return (hexToInt(c0) << 4) + hexToInt(c1); } //////////////////////////////////////////////////////////////////////////////// -int Lexer::hexToInt (int c0, int c1, int c2, int c3) -{ - return (hexToInt (c0) << 12) + - (hexToInt (c1) << 8) + - (hexToInt (c2) << 4) + - hexToInt (c3); +int Lexer::hexToInt(int c0, int c1, int c2, int c3) { + return (hexToInt(c0) << 12) + (hexToInt(c1) << 8) + (hexToInt(c2) << 4) + hexToInt(c3); } //////////////////////////////////////////////////////////////////////////////// @@ -308,16 +264,10 @@ int Lexer::hexToInt (int c0, int c1, int c2, int c3) // left: wonderful // right: wonderbread // returns: ^ 6 -std::string::size_type Lexer::commonLength ( - const std::string& left, - const std::string& right) -{ +std::string::size_type Lexer::commonLength(const std::string& left, const std::string& right) { std::string::size_type l = 0; std::string::size_type r = 0; - while (left[l] == right[r] && - utf8_next_char (left, l) && - utf8_next_char (right, r)) - ; + while (left[l] == right[r] && utf8_next_char(left, l) && utf8_next_char(right, r)); return l; } @@ -330,138 +280,106 @@ std::string::size_type Lexer::commonLength ( // right: prowonderbread // r: ^ // returns: ^ 6 -std::string::size_type Lexer::commonLength ( - const std::string& left, - std::string::size_type l, - const std::string& right, - std::string::size_type r) -{ - while (left[l] == right[r] && - utf8_next_char (left, l) && - utf8_next_char (right, r)) - ; +std::string::size_type Lexer::commonLength(const std::string& left, std::string::size_type l, + const std::string& right, std::string::size_type r) { + while (left[l] == right[r] && utf8_next_char(left, l) && utf8_next_char(right, r)); return l; } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::commify (const std::string& data) -{ +std::string Lexer::commify(const std::string& data) { // First scan for decimal point and end of digits. int decimalPoint = -1; - int end = -1; + int end = -1; int i; - for (int i = 0; i < (int) data.length (); ++i) - { - if (unicodeLatinDigit (data[i])) - end = i; + for (int i = 0; i < (int)data.length(); ++i) { + if (unicodeLatinDigit(data[i])) end = i; - if (data[i] == '.') - decimalPoint = i; + if (data[i] == '.') decimalPoint = i; } std::string result; - if (decimalPoint != -1) - { + if (decimalPoint != -1) { // In reverse order, transfer all digits up to, and including the decimal // point. - for (i = (int) data.length () - 1; i >= decimalPoint; --i) - result += data[i]; + for (i = (int)data.length() - 1; i >= decimalPoint; --i) result += data[i]; int consecutiveDigits = 0; - for (; i >= 0; --i) - { - if (unicodeLatinDigit (data[i])) - { + for (; i >= 0; --i) { + if (unicodeLatinDigit(data[i])) { result += data[i]; - if (++consecutiveDigits == 3 && i && unicodeLatinDigit (data[i - 1])) - { + if (++consecutiveDigits == 3 && i && unicodeLatinDigit(data[i - 1])) { result += ','; consecutiveDigits = 0; } - } - else + } else result += data[i]; } - } - else - { + } else { // In reverse order, transfer all digits up to, but not including the last // digit. - for (i = (int) data.length () - 1; i > end; --i) - result += data[i]; + for (i = (int)data.length() - 1; i > end; --i) result += data[i]; int consecutiveDigits = 0; - for (; i >= 0; --i) - { - if (unicodeLatinDigit (data[i])) - { + for (; i >= 0; --i) { + if (unicodeLatinDigit(data[i])) { result += data[i]; - if (++consecutiveDigits == 3 && i && unicodeLatinDigit (data[i - 1])) - { + if (++consecutiveDigits == 3 && i && unicodeLatinDigit(data[i - 1])) { result += ','; consecutiveDigits = 0; } - } - else + } else result += data[i]; } } // reverse result into data. std::string done; - for (int i = (int) result.length () - 1; i >= 0; --i) - done += result[i]; + for (int i = (int)result.length() - 1; i >= 0; --i) done += result[i]; return done; } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::lowerCase (const std::string& input) -{ +std::string Lexer::lowerCase(const std::string& input) { std::string output = input; - std::transform (output.begin (), output.end (), output.begin (), tolower); + std::transform(output.begin(), output.end(), output.begin(), tolower); return output; } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::ucFirst (const std::string& input) -{ +std::string Lexer::ucFirst(const std::string& input) { std::string output = input; - if (output.length () > 0) - output[0] = toupper (output[0]); + if (output.length() > 0) output[0] = toupper(output[0]); return output; } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::trimLeft (const std::string& in, const std::string& t /*= " "*/) -{ - std::string::size_type ws = in.find_first_not_of (t); - if (ws > 0) - { - std::string out {in}; - return out.erase (0, ws); +std::string Lexer::trimLeft(const std::string& in, const std::string& t /*= " "*/) { + std::string::size_type ws = in.find_first_not_of(t); + if (ws > 0) { + std::string out{in}; + return out.erase(0, ws); } return in; } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::trimRight (const std::string& in, const std::string& t /*= " "*/) -{ - std::string out {in}; - return out.erase (in.find_last_not_of (t) + 1); +std::string Lexer::trimRight(const std::string& in, const std::string& t /*= " "*/) { + std::string out{in}; + return out.erase(in.find_last_not_of(t) + 1); } //////////////////////////////////////////////////////////////////////////////// -std::string Lexer::trim (const std::string& in, const std::string& t /*= " "*/) -{ - return trimLeft (trimRight (in, t), t); +std::string Lexer::trim(const std::string& in, const std::string& t /*= " "*/) { + return trimLeft(trimRight(in, t), t); } //////////////////////////////////////////////////////////////////////////////// @@ -469,11 +387,9 @@ std::string Lexer::trim (const std::string& in, const std::string& t /*= " "*/) // '|" // [ U+XXXX | \uXXXX | \" | \' | \\ | \/ | \b | \f | \n | \r | \t | . ] // '|" -bool Lexer::isString (std::string& token, Lexer::Type& type, const std::string& quotes) -{ +bool Lexer::isString(std::string& token, Lexer::Type& type, const std::string& quotes) { std::size_t marker = _cursor; - if (readWord (_text, quotes, marker, token)) - { + if (readWord(_text, quotes, marker, token)) { type = Lexer::Type::string; _cursor = marker; return true; @@ -485,15 +401,13 @@ bool Lexer::isString (std::string& token, Lexer::Type& type, const std::string& //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::date // -bool Lexer::isDate (std::string& token, Lexer::Type& type) -{ +bool Lexer::isDate(std::string& token, Lexer::Type& type) { // Try an ISO date parse. std::size_t iso_i = 0; Datetime iso; - if (iso.parse (_text.substr (_cursor), iso_i, Lexer::dateFormat)) - { + if (iso.parse(_text.substr(_cursor), iso_i, Lexer::dateFormat)) { type = Lexer::Type::date; - token = _text.substr (_cursor, iso_i); + token = _text.substr(_cursor, iso_i); _cursor += iso_i; return true; } @@ -504,24 +418,21 @@ bool Lexer::isDate (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::duration // -bool Lexer::isDuration (std::string& token, Lexer::Type& type) -{ +bool Lexer::isDuration(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; std::string extractedToken; Lexer::Type extractedType; - if (isOperator (extractedToken, extractedType)) - { + if (isOperator(extractedToken, extractedType)) { _cursor = marker; return false; } marker = 0; Duration iso; - if (iso.parse (_text.substr (_cursor), marker)) - { + if (iso.parse(_text.substr(_cursor), marker)) { type = Lexer::Type::duration; - token = _text.substr (_cursor, marker); + token = _text.substr(_cursor, marker); _cursor += marker; return true; } @@ -541,30 +452,22 @@ bool Lexer::isDuration (std::string& token, Lexer::Type& type) // XXXXXXXX- // XXXXXXXX // Followed only by EOS, whitespace, or single character operator. -bool Lexer::isUUID (std::string& token, Lexer::Type& type, bool endBoundary) -{ +bool Lexer::isUUID(std::string& token, Lexer::Type& type, bool endBoundary) { std::size_t marker = _cursor; // Greedy. std::size_t i = 0; - for (; i < 36 && marker + i < _eos; i++) - { - if (uuid_pattern[i] == 'x') - { - if (! unicodeHexDigit (_text[marker + i])) - break; - } - else if (uuid_pattern[i] != _text[marker + i]) + for (; i < 36 && marker + i < _eos; i++) { + if (uuid_pattern[i] == 'x') { + if (!unicodeHexDigit(_text[marker + i])) break; + } else if (uuid_pattern[i] != _text[marker + i]) break; } - if (i >= uuid_min_length && - (! endBoundary || - ! _text[marker + i] || - unicodeWhitespace (_text[marker + i]) || - isSingleCharOperator (_text[marker + i]))) - { - token = _text.substr (_cursor, i); + if (i >= uuid_min_length && + (!endBoundary || !_text[marker + i] || unicodeWhitespace(_text[marker + i]) || + isSingleCharOperator(_text[marker + i]))) { + token = _text.substr(_cursor, i); type = Lexer::Type::uuid; _cursor += i; return true; @@ -576,22 +479,16 @@ bool Lexer::isUUID (std::string& token, Lexer::Type& type, bool endBoundary) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::hex // 0xX+ -bool Lexer::isHexNumber (std::string& token, Lexer::Type& type) -{ +bool Lexer::isHexNumber(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; - if (_eos - marker >= 3 && - _text[marker + 0] == '0' && - _text[marker + 1] == 'x') - { + if (_eos - marker >= 3 && _text[marker + 0] == '0' && _text[marker + 1] == 'x') { marker += 2; - while (unicodeHexDigit (_text[marker])) - ++marker; + while (unicodeHexDigit(_text[marker])) ++marker; - if (marker - _cursor > 2) - { - token = _text.substr (_cursor, marker - _cursor); + if (marker - _cursor > 2) { + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::hex; _cursor = marker; return true; @@ -608,57 +505,41 @@ bool Lexer::isHexNumber (std::string& token, Lexer::Type& type) // [ . \d+ ] // [ e|E [ +|- ] \d+ [ . \d+ ] ] // not followed by non-operator. -bool Lexer::isNumber (std::string& token, Lexer::Type& type) -{ +bool Lexer::isNumber(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; bool leading_zero = (_text[marker] == '0'); - if (unicodeLatinDigit (_text[marker])) - { + if (unicodeLatinDigit(_text[marker])) { ++marker; // Two (or more) digit number with a leading zero are not allowed - if (leading_zero && unicodeLatinDigit (_text[marker])) - return false; + if (leading_zero && unicodeLatinDigit(_text[marker])) return false; - while (unicodeLatinDigit (_text[marker])) - utf8_next_char (_text, marker); + while (unicodeLatinDigit(_text[marker])) utf8_next_char(_text, marker); - if (_text[marker] == '.') - { + if (_text[marker] == '.') { ++marker; - if (unicodeLatinDigit (_text[marker])) - { + if (unicodeLatinDigit(_text[marker])) { ++marker; - while (unicodeLatinDigit (_text[marker])) - utf8_next_char (_text, marker); + while (unicodeLatinDigit(_text[marker])) utf8_next_char(_text, marker); } } - if (_text[marker] == 'e' || - _text[marker] == 'E') - { + if (_text[marker] == 'e' || _text[marker] == 'E') { ++marker; - if (_text[marker] == '+' || - _text[marker] == '-') - ++marker; + if (_text[marker] == '+' || _text[marker] == '-') ++marker; - if (unicodeLatinDigit (_text[marker])) - { + if (unicodeLatinDigit(_text[marker])) { ++marker; - while (unicodeLatinDigit (_text[marker])) - utf8_next_char (_text, marker); + while (unicodeLatinDigit(_text[marker])) utf8_next_char(_text, marker); - if (_text[marker] == '.') - { + if (_text[marker] == '.') { ++marker; - if (unicodeLatinDigit (_text[marker])) - { + if (unicodeLatinDigit(_text[marker])) { ++marker; - while (unicodeLatinDigit (_text[marker])) - utf8_next_char (_text, marker); + while (unicodeLatinDigit(_text[marker])) utf8_next_char(_text, marker); } } } @@ -666,12 +547,10 @@ bool Lexer::isNumber (std::string& token, Lexer::Type& type) // Lookahead: ! | ! // If there is an immediately consecutive character, that is not an operator, fail. - if (_eos > marker && - ! unicodeWhitespace (_text[marker]) && - ! isSingleCharOperator (_text[marker])) + if (_eos > marker && !unicodeWhitespace(_text[marker]) && !isSingleCharOperator(_text[marker])) return false; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::number; _cursor = marker; return true; @@ -685,23 +564,19 @@ bool Lexer::isNumber (std::string& token, Lexer::Type& type) // 0 // [1-9]\d* // Integers do not start with a leading 0, unless they are zero. -bool Lexer::isInteger (std::string& token, Lexer::Type& type) -{ +bool Lexer::isInteger(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; bool leading_zero = (_text[marker] == '0'); - if (unicodeLatinDigit (_text[marker])) - { + if (unicodeLatinDigit(_text[marker])) { ++marker; - while (unicodeLatinDigit (_text[marker])) - utf8_next_char (_text, marker); + while (unicodeLatinDigit(_text[marker])) utf8_next_char(_text, marker); // Leading zero is only allowed in the case of number 0 - if (leading_zero and marker - _cursor > 1) - return false; + if (leading_zero and marker - _cursor > 1) return false; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::number; _cursor = marker; return true; @@ -713,12 +588,8 @@ bool Lexer::isInteger (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::separator // -- -bool Lexer::isSeparator (std::string& token, Lexer::Type& type) -{ - if (_eos - _cursor >= 2 && - _text[_cursor] == '-' && - _text[_cursor + 1] == '-') - { +bool Lexer::isSeparator(std::string& token, Lexer::Type& type) { + if (_eos - _cursor >= 2 && _text[_cursor] == '-' && _text[_cursor + 1] == '-') { _cursor += 2; type = Lexer::Type::separator; token = "--"; @@ -731,31 +602,23 @@ bool Lexer::isSeparator (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::url // http [s] :// ... -bool Lexer::isURL (std::string& token, Lexer::Type& type) -{ +bool Lexer::isURL(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; - if (_eos - _cursor > 9 && // length 'https://*' + if (_eos - _cursor > 9 && // length 'https://*' (_text[marker + 0] == 'h' || _text[marker + 0] == 'H') && (_text[marker + 1] == 't' || _text[marker + 1] == 'T') && (_text[marker + 2] == 't' || _text[marker + 2] == 'T') && - (_text[marker + 3] == 'p' || _text[marker + 3] == 'P')) - { + (_text[marker + 3] == 'p' || _text[marker + 3] == 'P')) { marker += 4; - if (_text[marker + 0] == 's' || _text[marker + 0] == 'S') - ++marker; + if (_text[marker + 0] == 's' || _text[marker + 0] == 'S') ++marker; - if (_text[marker + 0] == ':' && - _text[marker + 1] == '/' && - _text[marker + 2] == '/') - { + if (_text[marker + 0] == ':' && _text[marker + 1] == '/' && _text[marker + 2] == '/') { marker += 3; - while (marker < _eos && - ! unicodeWhitespace (_text[marker])) - utf8_next_char (_text, marker); + while (marker < _eos && !unicodeWhitespace(_text[marker])) utf8_next_char(_text, marker); - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::url; _cursor = marker; return true; @@ -769,33 +632,27 @@ bool Lexer::isURL (std::string& token, Lexer::Type& type) // Lexer::Type::pair // [ | ] // separator '::' | ':=' | ':' | '=' -bool Lexer::isPair (std::string& token, Lexer::Type& type) -{ +bool Lexer::isPair(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; std::string ignoredToken; Lexer::Type ignoredType; - if (isIdentifier (ignoredToken, ignoredType)) - { + if (isIdentifier(ignoredToken, ignoredType)) { // Look for a valid separator. - std::string separator = _text.substr (_cursor, 2); + std::string separator = _text.substr(_cursor, 2); if (separator == "::" || separator == ":=") _cursor += 2; else if (separator[0] == ':' || separator[0] == '=') _cursor++; - else - { + else { _cursor = marker; return false; } // String, word or nothing are all valid. - if (readWord (_text, "'\"", _cursor, ignoredToken) || - readWord (_text, _cursor, ignoredToken) || - isEOS () || - unicodeWhitespace (_text[_cursor])) - { - token = _text.substr (marker, _cursor - marker); + if (readWord(_text, "'\"", _cursor, ignoredToken) || readWord(_text, _cursor, ignoredToken) || + isEOS() || unicodeWhitespace(_text[_cursor])) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::pair; return true; } @@ -813,81 +670,62 @@ bool Lexer::isPair (std::string& token, Lexer::Type& type) // or a combination: 1,3,5-10 // // [ - ] [ , [ - ] ] ... -bool Lexer::isSet (std::string& token, Lexer::Type& type) -{ +bool Lexer::isSet(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; int count = 0; std::string dummyToken; Lexer::Type dummyType; - do - { - if (isInteger (dummyToken, dummyType)) - { + do { + if (isInteger(dummyToken, dummyType)) { ++count; - if (isLiteral ("-", false, false)) - { - if (isInteger (dummyToken, dummyType)) + if (isLiteral("-", false, false)) { + if (isInteger(dummyToken, dummyType)) ++count; - else - { + else { _cursor = marker; return false; } } - } - else - { + } else { _cursor = marker; return false; } - } - while (isLiteral (",", false, false)); + } while (isLiteral(",", false, false)); // Success is multiple numbers, matching the pattern. - if (count > 1 && - (isEOS () || - unicodeWhitespace (_text[_cursor]) || - isHardBoundary (_text[_cursor], _text[_cursor + 1]))) - { - token = _text.substr (marker, _cursor - marker); + if (count > 1 && (isEOS() || unicodeWhitespace(_text[_cursor]) || + isHardBoundary(_text[_cursor], _text[_cursor + 1]))) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::set; return true; } _cursor = marker; return false; - } //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::tag // ^ | '(' | ')' | // [ +|- ] [ ]* -bool Lexer::isTag (std::string& token, Lexer::Type& type) -{ +bool Lexer::isTag(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; // Lookbehind: Assert ^ or preceded by whitespace, (, or ). - if (marker > 0 && - ! unicodeWhitespace (_text[marker - 1]) && - _text[marker - 1] != '(' && + if (marker > 0 && !unicodeWhitespace(_text[marker - 1]) && _text[marker - 1] != '(' && _text[marker - 1] != ')') return false; - if (_text[marker] == '+' || - _text[marker] == '-') - { + if (_text[marker] == '+' || _text[marker] == '-') { ++marker; - if (isIdentifierStart (_text[marker])) - { - utf8_next_char (_text, marker); + if (isIdentifierStart(_text[marker])) { + utf8_next_char(_text, marker); - while (isIdentifierNext (_text[marker])) - utf8_next_char (_text, marker); + while (isIdentifierNext(_text[marker])) utf8_next_char(_text, marker); - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::tag; _cursor = marker; return true; @@ -900,40 +738,28 @@ bool Lexer::isTag (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::path // ( / )+ -bool Lexer::isPath (std::string& token, Lexer::Type& type) -{ +bool Lexer::isPath(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; int slashCount = 0; - while (true) - { - if (_text[marker] == '/') - { + while (true) { + if (_text[marker] == '/') { ++marker; ++slashCount; - } - else + } else break; - if (_text[marker] && - ! unicodeWhitespace (_text[marker]) && - _text[marker] != '/') - { - utf8_next_char (_text, marker); - while (_text[marker] && - ! unicodeWhitespace (_text[marker]) && - _text[marker] != '/') - utf8_next_char (_text, marker); - } - else + if (_text[marker] && !unicodeWhitespace(_text[marker]) && _text[marker] != '/') { + utf8_next_char(_text, marker); + while (_text[marker] && !unicodeWhitespace(_text[marker]) && _text[marker] != '/') + utf8_next_char(_text, marker); + } else break; } - if (marker > _cursor && - slashCount > 3) - { + if (marker > _cursor && slashCount > 3) { type = Lexer::Type::path; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } @@ -944,25 +770,19 @@ bool Lexer::isPath (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::substitution // / / / [g] | -bool Lexer::isSubstitution (std::string& token, Lexer::Type& type) -{ +bool Lexer::isSubstitution(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; std::string word; - if (readWord (_text, "/", _cursor, word)) - { + if (readWord(_text, "/", _cursor, word)) { --_cursor; // Step backwards over the '/'. - if (readWord (_text, "/", _cursor, word)) - { - if (_text[_cursor] == 'g') - ++_cursor; + if (readWord(_text, "/", _cursor, word)) { + if (_text[_cursor] == 'g') ++_cursor; // Lookahread: | - if (_text[_cursor] == '\0' || - unicodeWhitespace (_text[_cursor])) - { - token = _text.substr (marker, _cursor - marker); + if (_text[_cursor] == '\0' || unicodeWhitespace(_text[_cursor])) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::substitution; return true; } @@ -976,16 +796,12 @@ bool Lexer::isSubstitution (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::pattern // / / | -bool Lexer::isPattern (std::string& token, Lexer::Type& type) -{ +bool Lexer::isPattern(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; std::string word; - if (readWord (_text, "/", _cursor, word) && - (isEOS () || - unicodeWhitespace (_text[_cursor]))) - { - token = _text.substr (marker, _cursor - marker); + if (readWord(_text, "/", _cursor, word) && (isEOS() || unicodeWhitespace(_text[_cursor]))) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::pattern; return true; } @@ -1000,68 +816,60 @@ bool Lexer::isPattern (std::string& token, Lexer::Type& type) // | // | // | -bool Lexer::isOperator (std::string& token, Lexer::Type& type) -{ +bool Lexer::isOperator(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; - if (_eos - marker >= 8 && _text.substr (marker, 8) == "_hastag_") - { + if (_eos - marker >= 8 && _text.substr(marker, 8) == "_hastag_") { marker += 8; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } - else if (_eos - marker >= 7 && _text.substr (marker, 7) == "_notag_") - { + else if (_eos - marker >= 7 && _text.substr(marker, 7) == "_notag_") { marker += 7; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } - else if (_eos - marker >= 5 && _text.substr (marker, 5) == "_neg_") - { + else if (_eos - marker >= 5 && _text.substr(marker, 5) == "_neg_") { marker += 5; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } - else if (_eos - marker >= 5 && _text.substr (marker, 5) == "_pos_") - { + else if (_eos - marker >= 5 && _text.substr(marker, 5) == "_pos_") { marker += 5; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } - else if (_eos - marker >= 3 && - isTripleCharOperator (_text[marker], _text[marker + 1], _text[marker + 2], _text[marker + 3])) - { + else if (_eos - marker >= 3 && isTripleCharOperator(_text[marker], _text[marker + 1], + _text[marker + 2], _text[marker + 3])) { marker += 3; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } else if (_eos - marker >= 2 && - isDoubleCharOperator (_text[marker], _text[marker + 1], _text[marker + 2])) - { + isDoubleCharOperator(_text[marker], _text[marker + 1], _text[marker + 2])) { marker += 2; type = Lexer::Type::op; - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); _cursor = marker; return true; } - else if (isSingleCharOperator (_text[marker])) - { + else if (isSingleCharOperator(_text[marker])) { token = _text[marker]; type = Lexer::Type::op; _cursor = ++marker; @@ -1115,38 +923,25 @@ bool Lexer::isOperator (std::string& token, Lexer::Type& type) // annotations..entry // annotations..description // -bool Lexer::isDOM (std::string& token, Lexer::Type& type) -{ +bool Lexer::isDOM(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; // rc. ... std::string partialToken; Lexer::Type partialType; - if (isLiteral ("rc.", false, false) && - isWord (partialToken, partialType)) - { - token = _text.substr (marker, _cursor - marker); + if (isLiteral("rc.", false, false) && isWord(partialToken, partialType)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; - } - else + } else _cursor = marker; // Literals - if (isOneOf ({"tw.syncneeded", - "tw.program", - "tw.args", - "tw.width", - "tw.height", - "tw.version", - "context.program", - "context.args", - "context.width", - "context.height", - "system.version", - "system.os"}, false, true)) - { - token = _text.substr (marker, _cursor - marker); + if (isOneOf({"tw.syncneeded", "tw.program", "tw.args", "tw.width", "tw.height", "tw.version", + "context.program", "context.args", "context.width", "context.height", + "system.version", "system.os"}, + false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } @@ -1156,11 +951,8 @@ bool Lexer::isDOM (std::string& token, Lexer::Type& type) // . std::string extractedToken; Lexer::Type extractedType; - if (isUUID (extractedToken, extractedType, false) || - isInteger (extractedToken, extractedType)) - { - if (! isLiteral (".", false, false)) - { + if (isUUID(extractedToken, extractedType, false) || isInteger(extractedToken, extractedType)) { + if (!isLiteral(".", false, false)) { _cursor = marker; return false; } @@ -1170,40 +962,31 @@ bool Lexer::isDOM (std::string& token, Lexer::Type& type) std::size_t checkpoint = _cursor; // [prefix]tags. - if (isLiteral ("tags", false, false) && - isLiteral (".", false, false) && - isWord (partialToken, partialType)) - { - token = _text.substr (marker, _cursor - marker); + if (isLiteral("tags", false, false) && isLiteral(".", false, false) && + isWord(partialToken, partialType)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; - } - else + } else _cursor = checkpoint; // [prefix]attribute (bounded) - if (isOneOf (attributes, false, true)) - { - token = _text.substr (marker, _cursor - marker); + if (isOneOf(attributes, false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } // [prefix]attribute. (unbounded) - if (isOneOf (attributes, false, false)) - { - if (isLiteral (".", false, false)) - { - std::string attribute = _text.substr (checkpoint, _cursor - checkpoint - 1); + if (isOneOf(attributes, false, false)) { + if (isLiteral(".", false, false)) { + std::string attribute = _text.substr(checkpoint, _cursor - checkpoint - 1); // if attribute type is 'date', then it has sub-elements. if (attributes[attribute] == "date" && - isOneOf ({"year", "month", "day", - "week", "weekday", - "julian", - "hour", "minute", "second"}, false, true)) - { - token = _text.substr (marker, _cursor - marker); + isOneOf({"year", "month", "day", "week", "weekday", "julian", "hour", "minute", "second"}, + false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } @@ -1212,9 +995,8 @@ bool Lexer::isDOM (std::string& token, Lexer::Type& type) } // Lookahead: ! - else if (! unicodeLatinAlpha (_text[marker])) - { - token = _text.substr (marker, _cursor - marker); + else if (!unicodeLatinAlpha(_text[marker])) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } @@ -1223,48 +1005,35 @@ bool Lexer::isDOM (std::string& token, Lexer::Type& type) } // [prefix]annotations. - if (isLiteral ("annotations", true, false) && - isLiteral (".", false, false)) - { - if (isLiteral ("count", false, false)) - { - token = _text.substr (marker, _cursor - marker); + if (isLiteral("annotations", true, false) && isLiteral(".", false, false)) { + if (isLiteral("count", false, false)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } std::string extractedToken; Lexer::Type extractedType; - if (isInteger (extractedToken, extractedType)) - { - if (isLiteral (".", false, false)) - { - if (isLiteral ("description", false, true)) - { - token = _text.substr (marker, _cursor - marker); + if (isInteger(extractedToken, extractedType)) { + if (isLiteral(".", false, false)) { + if (isLiteral("description", false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; - } - else if (isLiteral ("entry", false, true)) - { - token = _text.substr (marker, _cursor - marker); + } else if (isLiteral("entry", false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; - } - else if (isLiteral ("entry", false, false) && - isLiteral (".", false, false) && - isOneOf ({"year", "month", "day", - "week", "weekday", - "julian", - "hour", "minute", "second"}, false, true)) - { - token = _text.substr (marker, _cursor - marker); + } else if (isLiteral("entry", false, false) && isLiteral(".", false, false) && + isOneOf({"year", "month", "day", "week", "weekday", "julian", "hour", "minute", + "second"}, + false, true)) { + token = _text.substr(marker, _cursor - marker); type = Lexer::Type::dom; return true; } } - } - else + } else _cursor = checkpoint; } @@ -1275,18 +1044,15 @@ bool Lexer::isDOM (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::identifier // [ ]* -bool Lexer::isIdentifier (std::string& token, Lexer::Type& type) -{ +bool Lexer::isIdentifier(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; - if (isIdentifierStart (_text[marker])) - { - utf8_next_char (_text, marker); + if (isIdentifierStart(_text[marker])) { + utf8_next_char(_text, marker); - while (isIdentifierNext (_text[marker])) - utf8_next_char (_text, marker); + while (isIdentifierNext(_text[marker])) utf8_next_char(_text, marker); - token = _text.substr (_cursor, marker - _cursor); + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::identifier; _cursor = marker; return true; @@ -1298,18 +1064,14 @@ bool Lexer::isIdentifier (std::string& token, Lexer::Type& type) //////////////////////////////////////////////////////////////////////////////// // Lexer::Type::word // [^\s]+ -bool Lexer::isWord (std::string& token, Lexer::Type& type) -{ +bool Lexer::isWord(std::string& token, Lexer::Type& type) { std::size_t marker = _cursor; - while (_text[marker] && - ! unicodeWhitespace (_text[marker]) && - ! isSingleCharOperator (_text[marker])) - utf8_next_char (_text, marker); + while (_text[marker] && !unicodeWhitespace(_text[marker]) && !isSingleCharOperator(_text[marker])) + utf8_next_char(_text, marker); - if (marker > _cursor) - { - token = _text.substr (_cursor, marker - _cursor); + if (marker > _cursor) { + token = _text.substr(_cursor, marker - _cursor); type = Lexer::Type::word; _cursor = marker; return true; @@ -1319,28 +1081,18 @@ bool Lexer::isWord (std::string& token, Lexer::Type& type) } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isLiteral ( - const std::string& literal, - bool allowAbbreviations, - bool endBoundary) -{ - auto common = commonLength (literal, 0, _text, _cursor); +bool Lexer::isLiteral(const std::string& literal, bool allowAbbreviations, bool endBoundary) { + auto common = commonLength(literal, 0, _text, _cursor); // Without abbreviations, common must equal literal length. - if (! allowAbbreviations && - common < literal.length ()) - return false; + if (!allowAbbreviations && common < literal.length()) return false; // Abbreviations must meet the minimum size. - if (allowAbbreviations && - common < minimumMatchLength) - return false; + if (allowAbbreviations && common < minimumMatchLength) return false; // End boundary conditions must be met. - if (endBoundary && - _text[_cursor + common] && - ! unicodeWhitespace (_text[_cursor + common]) && - ! Lexer::isSingleCharOperator (_text[_cursor + common])) + if (endBoundary && _text[_cursor + common] && !unicodeWhitespace(_text[_cursor + common]) && + !Lexer::isSingleCharOperator(_text[_cursor + common])) return false; _cursor += common; @@ -1348,76 +1100,81 @@ bool Lexer::isLiteral ( } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isOneOf ( - const std::vector & options, - bool allowAbbreviations, - bool endBoundary) -{ +bool Lexer::isOneOf(const std::vector& options, bool allowAbbreviations, + bool endBoundary) { for (auto& item : options) - if (isLiteral (item, allowAbbreviations, endBoundary)) - return true; + if (isLiteral(item, allowAbbreviations, endBoundary)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isOneOf ( - const std::map & options, - bool allowAbbreviations, - bool endBoundary) -{ +bool Lexer::isOneOf(const std::map& options, bool allowAbbreviations, + bool endBoundary) { for (auto& item : options) - if (isLiteral (item.first, allowAbbreviations, endBoundary)) - return true; + if (isLiteral(item.first, allowAbbreviations, endBoundary)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// // Static -std::string Lexer::typeToString (Lexer::Type type) -{ - if (type == Lexer::Type::string) return std::string ("\033[38;5;7m\033[48;5;3m") + "string" + "\033[0m"; - else if (type == Lexer::Type::uuid) return std::string ("\033[38;5;7m\033[48;5;10m") + "uuid" + "\033[0m"; - else if (type == Lexer::Type::hex) return std::string ("\033[38;5;7m\033[48;5;14m") + "hex" + "\033[0m"; - else if (type == Lexer::Type::number) return std::string ("\033[38;5;7m\033[48;5;6m") + "number" + "\033[0m"; - else if (type == Lexer::Type::separator) return std::string ("\033[38;5;7m\033[48;5;4m") + "separator" + "\033[0m"; - else if (type == Lexer::Type::url) return std::string ("\033[38;5;7m\033[48;5;4m") + "url" + "\033[0m"; - else if (type == Lexer::Type::pair) return std::string ("\033[38;5;7m\033[48;5;1m") + "pair" + "\033[0m"; - else if (type == Lexer::Type::set) return std::string ("\033[38;5;15m\033[48;5;208m") + "set" + "\033[0m"; - else if (type == Lexer::Type::tag) return std::string ("\033[37;45m") + "tag" + "\033[0m"; - else if (type == Lexer::Type::path) return std::string ("\033[37;102m") + "path" + "\033[0m"; - else if (type == Lexer::Type::substitution) return std::string ("\033[37;102m") + "substitution" + "\033[0m"; - else if (type == Lexer::Type::pattern) return std::string ("\033[37;42m") + "pattern" + "\033[0m"; - else if (type == Lexer::Type::op) return std::string ("\033[38;5;7m\033[48;5;203m") + "op" + "\033[0m"; - else if (type == Lexer::Type::dom) return std::string ("\033[38;5;15m\033[48;5;244m") + "dom" + "\033[0m"; - else if (type == Lexer::Type::identifier) return std::string ("\033[38;5;15m\033[48;5;244m") + "identifier" + "\033[0m"; - else if (type == Lexer::Type::word) return std::string ("\033[38;5;15m\033[48;5;236m") + "word" + "\033[0m"; - else if (type == Lexer::Type::date) return std::string ("\033[38;5;15m\033[48;5;34m") + "date" + "\033[0m"; - else if (type == Lexer::Type::duration) return std::string ("\033[38;5;15m\033[48;5;34m") + "duration" + "\033[0m"; - else return std::string ("\033[37;41m") + "unknown" + "\033[0m"; +std::string Lexer::typeToString(Lexer::Type type) { + if (type == Lexer::Type::string) + return std::string("\033[38;5;7m\033[48;5;3m") + "string" + "\033[0m"; + else if (type == Lexer::Type::uuid) + return std::string("\033[38;5;7m\033[48;5;10m") + "uuid" + "\033[0m"; + else if (type == Lexer::Type::hex) + return std::string("\033[38;5;7m\033[48;5;14m") + "hex" + "\033[0m"; + else if (type == Lexer::Type::number) + return std::string("\033[38;5;7m\033[48;5;6m") + "number" + "\033[0m"; + else if (type == Lexer::Type::separator) + return std::string("\033[38;5;7m\033[48;5;4m") + "separator" + "\033[0m"; + else if (type == Lexer::Type::url) + return std::string("\033[38;5;7m\033[48;5;4m") + "url" + "\033[0m"; + else if (type == Lexer::Type::pair) + return std::string("\033[38;5;7m\033[48;5;1m") + "pair" + "\033[0m"; + else if (type == Lexer::Type::set) + return std::string("\033[38;5;15m\033[48;5;208m") + "set" + "\033[0m"; + else if (type == Lexer::Type::tag) + return std::string("\033[37;45m") + "tag" + "\033[0m"; + else if (type == Lexer::Type::path) + return std::string("\033[37;102m") + "path" + "\033[0m"; + else if (type == Lexer::Type::substitution) + return std::string("\033[37;102m") + "substitution" + "\033[0m"; + else if (type == Lexer::Type::pattern) + return std::string("\033[37;42m") + "pattern" + "\033[0m"; + else if (type == Lexer::Type::op) + return std::string("\033[38;5;7m\033[48;5;203m") + "op" + "\033[0m"; + else if (type == Lexer::Type::dom) + return std::string("\033[38;5;15m\033[48;5;244m") + "dom" + "\033[0m"; + else if (type == Lexer::Type::identifier) + return std::string("\033[38;5;15m\033[48;5;244m") + "identifier" + "\033[0m"; + else if (type == Lexer::Type::word) + return std::string("\033[38;5;15m\033[48;5;236m") + "word" + "\033[0m"; + else if (type == Lexer::Type::date) + return std::string("\033[38;5;15m\033[48;5;34m") + "date" + "\033[0m"; + else if (type == Lexer::Type::duration) + return std::string("\033[38;5;15m\033[48;5;34m") + "duration" + "\033[0m"; + else + return std::string("\033[37;41m") + "unknown" + "\033[0m"; } //////////////////////////////////////////////////////////////////////////////// -bool Lexer::isAllDigits (const std::string& text) -{ - return text.length () && - text.find_first_not_of ("0123456789") == std::string::npos; +bool Lexer::isAllDigits(const std::string& text) { + return text.length() && text.find_first_not_of("0123456789") == std::string::npos; } //////////////////////////////////////////////////////////////////////////////// // This is intentionally looking for a single token. -bool Lexer::isDOM (const std::string& text) -{ - Lexer lex (text); +bool Lexer::isDOM(const std::string& text) { + Lexer lex(text); int count = 0; std::string token; Lexer::Type type; - while (lex.token (token, type)) - ++count; + while (lex.token(token, type)) ++count; - return count == 1 && - type == Lexer::Type::dom; + return count == 1 && type == Lexer::Type::dom; } //////////////////////////////////////////////////////////////////////////////// @@ -1428,80 +1185,92 @@ bool Lexer::isDOM (const std::string& text) // "\"" // 'one two' // Result includes the quotes. -bool Lexer::readWord ( - const std::string& text, - const std::string& quotes, - std::string::size_type& cursor, - std::string& word) -{ - if (quotes.find (text[cursor]) == std::string::npos) - return false; +bool Lexer::readWord(const std::string& text, const std::string& quotes, + std::string::size_type& cursor, std::string& word) { + if (quotes.find(text[cursor]) == std::string::npos) return false; - std::string::size_type eos = text.length (); + std::string::size_type eos = text.length(); int quote = text[cursor++]; word = quote; int c; - while ((c = text[cursor])) - { + while ((c = text[cursor])) { // Quoted word ends on a quote. - if (quote && quote == c) - { - word += utf8_character (utf8_next_char (text, cursor)); + if (quote && quote == c) { + word += utf8_character(utf8_next_char(text, cursor)); break; } // Unicode U+XXXX or \uXXXX codepoint. else if (eos - cursor >= 6 && - ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') || + ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') || (text[cursor + 0] == '\\' && text[cursor + 1] == 'u')) && - unicodeHexDigit (text[cursor + 2]) && - unicodeHexDigit (text[cursor + 3]) && - unicodeHexDigit (text[cursor + 4]) && - unicodeHexDigit (text[cursor + 5])) - { - word += utf8_character ( - hexToInt ( - text[cursor + 2], - text[cursor + 3], - text[cursor + 4], - text[cursor + 5])); + unicodeHexDigit(text[cursor + 2]) && unicodeHexDigit(text[cursor + 3]) && + unicodeHexDigit(text[cursor + 4]) && unicodeHexDigit(text[cursor + 5])) { + word += utf8_character( + hexToInt(text[cursor + 2], text[cursor + 3], text[cursor + 4], text[cursor + 5])); cursor += 6; } // An escaped thing. - else if (c == '\\') - { + else if (c == '\\') { c = text[++cursor]; - switch (c) - { - case '"': word += (char) 0x22; ++cursor; break; - case '\'': word += (char) 0x27; ++cursor; break; - case '\\': word += (char) 0x5C; ++cursor; break; - case 'b': word += (char) 0x08; ++cursor; break; - case 'f': word += (char) 0x0C; ++cursor; break; - case 'n': word += (char) 0x0A; ++cursor; break; - case 'r': word += (char) 0x0D; ++cursor; break; - case 't': word += (char) 0x09; ++cursor; break; - case 'v': word += (char) 0x0B; ++cursor; break; + switch (c) { + case '"': + word += (char)0x22; + ++cursor; + break; + case '\'': + word += (char)0x27; + ++cursor; + break; + case '\\': + word += (char)0x5C; + ++cursor; + break; + case 'b': + word += (char)0x08; + ++cursor; + break; + case 'f': + word += (char)0x0C; + ++cursor; + break; + case 'n': + word += (char)0x0A; + ++cursor; + break; + case 'r': + word += (char)0x0D; + ++cursor; + break; + case 't': + word += (char)0x09; + ++cursor; + break; + case 'v': + word += (char)0x0B; + ++cursor; + break; - // This pass-through default case means that anything can be escaped - // harmlessly. In particular 'quote' is included, if it not one of the - // above characters. - default: word += (char) c; ++cursor; break; + // This pass-through default case means that anything can be escaped + // harmlessly. In particular 'quote' is included, if it not one of the + // above characters. + default: + word += (char)c; + ++cursor; + break; } } // Ordinary character. else - word += utf8_character (utf8_next_char (text, cursor)); + word += utf8_character(utf8_next_char(text, cursor)); } // Verify termination. - return word[0] == quote && - word[word.length () - 1] == quote && - word.length () >= 2; + return word[0] == quote && word[word.length() - 1] == quote && word.length() >= 2; } //////////////////////////////////////////////////////////////////////////////// @@ -1515,12 +1284,8 @@ bool Lexer::readWord ( // Lexer::isEOS // unicodeWhitespace // Lexer::isHardBoundary -bool Lexer::readWord ( - const std::string& text, - std::string::size_type& cursor, - std::string& word) -{ - std::string::size_type eos = text.length (); +bool Lexer::readWord(const std::string& text, std::string::size_type& cursor, std::string& word) { + std::string::size_type eos = text.length(); word = ""; int c; @@ -1528,139 +1293,140 @@ bool Lexer::readWord ( while ((c = text[cursor])) // Handles EOS. { // Unquoted word ends on white space. - if (unicodeWhitespace (c)) - break; + if (unicodeWhitespace(c)) break; // Parentheses mostly. - if (prev && Lexer::isHardBoundary (prev, c)) - break; + if (prev && Lexer::isHardBoundary(prev, c)) break; // Unicode U+XXXX or \uXXXX codepoint. else if (eos - cursor >= 6 && - ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') || + ((text[cursor + 0] == 'U' && text[cursor + 1] == '+') || (text[cursor + 0] == '\\' && text[cursor + 1] == 'u')) && - unicodeHexDigit (text[cursor + 2]) && - unicodeHexDigit (text[cursor + 3]) && - unicodeHexDigit (text[cursor + 4]) && - unicodeHexDigit (text[cursor + 5])) - { - word += utf8_character ( - hexToInt ( - text[cursor + 2], - text[cursor + 3], - text[cursor + 4], - text[cursor + 5])); + unicodeHexDigit(text[cursor + 2]) && unicodeHexDigit(text[cursor + 3]) && + unicodeHexDigit(text[cursor + 4]) && unicodeHexDigit(text[cursor + 5])) { + word += utf8_character( + hexToInt(text[cursor + 2], text[cursor + 3], text[cursor + 4], text[cursor + 5])); cursor += 6; } // An escaped thing. - else if (c == '\\') - { + else if (c == '\\') { c = text[++cursor]; - switch (c) - { - case '"': word += (char) 0x22; ++cursor; break; - case '\'': word += (char) 0x27; ++cursor; break; - case '\\': word += (char) 0x5C; ++cursor; break; - case 'b': word += (char) 0x08; ++cursor; break; - case 'f': word += (char) 0x0C; ++cursor; break; - case 'n': word += (char) 0x0A; ++cursor; break; - case 'r': word += (char) 0x0D; ++cursor; break; - case 't': word += (char) 0x09; ++cursor; break; - case 'v': word += (char) 0x0B; ++cursor; break; + switch (c) { + case '"': + word += (char)0x22; + ++cursor; + break; + case '\'': + word += (char)0x27; + ++cursor; + break; + case '\\': + word += (char)0x5C; + ++cursor; + break; + case 'b': + word += (char)0x08; + ++cursor; + break; + case 'f': + word += (char)0x0C; + ++cursor; + break; + case 'n': + word += (char)0x0A; + ++cursor; + break; + case 'r': + word += (char)0x0D; + ++cursor; + break; + case 't': + word += (char)0x09; + ++cursor; + break; + case 'v': + word += (char)0x0B; + ++cursor; + break; - // This pass-through default case means that anything can be escaped - // harmlessly. In particular 'quote' is included, if it not one of the - // above characters. - default: word += (char) c; ++cursor; break; + // This pass-through default case means that anything can be escaped + // harmlessly. In particular 'quote' is included, if it not one of the + // above characters. + default: + word += (char)c; + ++cursor; + break; } } // Ordinary character. else - word += utf8_character (utf8_next_char (text, cursor)); + word += utf8_character(utf8_next_char(text, cursor)); prev = c; } - return word.length () > 0 ? true : false; + return word.length() > 0 ? true : false; } //////////////////////////////////////////////////////////////////////////////// // [. ] <: | = | :: | :=> [] -bool Lexer::decomposePair ( - const std::string& text, - std::string& name, - std::string& modifier, - std::string& separator, - std::string& value) -{ +bool Lexer::decomposePair(const std::string& text, std::string& name, std::string& modifier, + std::string& separator, std::string& value) { // Look for the required elements. - std::string::size_type dot = text.find ('.'); - std::string::size_type sep_defer = text.find ("::"); - std::string::size_type sep_eval = text.find (":="); - std::string::size_type sep_colon = text.find (':'); - std::string::size_type sep_equal = text.find ('='); + std::string::size_type dot = text.find('.'); + std::string::size_type sep_defer = text.find("::"); + std::string::size_type sep_eval = text.find(":="); + std::string::size_type sep_colon = text.find(':'); + std::string::size_type sep_equal = text.find('='); // Determine which separator is dominant, which would be the first one seen, // taking into consideration the overlapping : characters. - std::string::size_type sep = std::string::npos; + std::string::size_type sep = std::string::npos; std::string::size_type sep_end = std::string::npos; - if (sep_defer != std::string::npos && - (sep_eval == std::string::npos || sep_defer <= sep_eval) && + if (sep_defer != std::string::npos && (sep_eval == std::string::npos || sep_defer <= sep_eval) && (sep_colon == std::string::npos || sep_defer <= sep_colon) && - (sep_equal == std::string::npos || sep_defer <= sep_equal)) - { - sep = sep_defer; + (sep_equal == std::string::npos || sep_defer <= sep_equal)) { + sep = sep_defer; sep_end = sep_defer + 2; - } - else if (sep_eval != std::string::npos && - (sep_defer == std::string::npos || sep_eval <= sep_defer) && - (sep_colon == std::string::npos || sep_eval <= sep_colon) && - (sep_equal == std::string::npos || sep_eval <= sep_equal)) - { - sep = sep_eval; + } else if (sep_eval != std::string::npos && + (sep_defer == std::string::npos || sep_eval <= sep_defer) && + (sep_colon == std::string::npos || sep_eval <= sep_colon) && + (sep_equal == std::string::npos || sep_eval <= sep_equal)) { + sep = sep_eval; sep_end = sep_eval + 2; - } - else if (sep_colon != std::string::npos && - (sep_defer == std::string::npos || sep_colon <= sep_defer) && - (sep_eval == std::string::npos || sep_colon <= sep_eval) && - (sep_equal == std::string::npos || sep_colon <= sep_equal)) - { - sep = sep_colon; + } else if (sep_colon != std::string::npos && + (sep_defer == std::string::npos || sep_colon <= sep_defer) && + (sep_eval == std::string::npos || sep_colon <= sep_eval) && + (sep_equal == std::string::npos || sep_colon <= sep_equal)) { + sep = sep_colon; sep_end = sep_colon + 1; - } - else if (sep_equal != std::string::npos && - (sep_defer == std::string::npos || sep_equal <= sep_defer) && - (sep_eval == std::string::npos || sep_equal <= sep_eval) && - (sep_colon == std::string::npos || sep_equal <= sep_colon)) - { - sep = sep_equal; + } else if (sep_equal != std::string::npos && + (sep_defer == std::string::npos || sep_equal <= sep_defer) && + (sep_eval == std::string::npos || sep_equal <= sep_eval) && + (sep_colon == std::string::npos || sep_equal <= sep_colon)) { + sep = sep_equal; sep_end = sep_equal + 1; } // If sep is known, all is well. - if (sep != std::string::npos) - { + if (sep != std::string::npos) { // Now the only unknown is whethere there is a modifier. - if (dot != std::string::npos && dot < sep) - { - name = text.substr (0, dot); - modifier = text.substr (dot + 1, sep - dot - 1); - } - else - { - name = text.substr (0, sep); + if (dot != std::string::npos && dot < sep) { + name = text.substr(0, dot); + modifier = text.substr(dot + 1, sep - dot - 1); + } else { + name = text.substr(0, sep); modifier = ""; } - separator = text.substr (sep, sep_end - sep); - value = text.substr (sep_end); + separator = text.substr(sep, sep_end - sep); + value = text.substr(sep_end); // An empty name is an error. - if (name.length ()) - return true; + if (name.length()) return true; } return false; @@ -1668,29 +1434,21 @@ bool Lexer::decomposePair ( //////////////////////////////////////////////////////////////////////////////// // / / / [] -bool Lexer::decomposeSubstitution ( - const std::string& text, - std::string& from, - std::string& to, - std::string& flags) -{ +bool Lexer::decomposeSubstitution(const std::string& text, std::string& from, std::string& to, + std::string& flags) { std::string parsed_from; std::string::size_type cursor = 0; - if (readWord (text, "/", cursor, parsed_from) && - parsed_from.length ()) - { + if (readWord(text, "/", cursor, parsed_from) && parsed_from.length()) { --cursor; std::string parsed_to; - if (readWord (text, "/", cursor, parsed_to)) - { - std::string parsed_flags = text.substr (cursor); - if (parsed_flags.find ('/') == std::string::npos) - { - dequote (parsed_from, "/"); - dequote (parsed_to, "/"); + if (readWord(text, "/", cursor, parsed_to)) { + std::string parsed_flags = text.substr(cursor); + if (parsed_flags.find('/') == std::string::npos) { + dequote(parsed_from, "/"); + dequote(parsed_to, "/"); - from = parsed_from; - to = parsed_to; + from = parsed_from; + to = parsed_to; flags = parsed_flags; return true; } @@ -1702,21 +1460,14 @@ bool Lexer::decomposeSubstitution ( //////////////////////////////////////////////////////////////////////////////// // / / [] -bool Lexer::decomposePattern ( - const std::string& text, - std::string& pattern, - std::string& flags) -{ +bool Lexer::decomposePattern(const std::string& text, std::string& pattern, std::string& flags) { std::string ignored; std::string::size_type cursor = 0; - if (readWord (text, "/", cursor, ignored) && - ignored.length ()) - { - auto parsed_flags = text.substr (cursor); - if (parsed_flags.find ('/') == std::string::npos) - { - flags = parsed_flags; - pattern = text.substr (1, cursor - 2 - flags.length ()); + if (readWord(text, "/", cursor, ignored) && ignored.length()) { + auto parsed_flags = text.substr(cursor); + if (parsed_flags.find('/') == std::string::npos) { + flags = parsed_flags; + pattern = text.substr(1, cursor - 2 - flags.length()); return true; } } diff --git a/src/Lexer.h b/src/Lexer.h index 790502eff..f7cfe219d 100644 --- a/src/Lexer.h +++ b/src/Lexer.h @@ -27,95 +27,108 @@ #ifndef INCLUDED_LEXER #define INCLUDED_LEXER -#include -#include -#include #include +#include +#include +#include // Lexer: A UTF8 lexical analyzer for every construct used on the Taskwarrior // command line, with additional recognized types for disambiguation. -class Lexer -{ -public: +class Lexer { + public: // These are overridable. static std::string dateFormat; static std::string::size_type minimumMatchLength; - static std::map attributes; + static std::map attributes; - enum class Type { uuid, number, hex, - string, - url, pair, set, separator, - tag, - path, - substitution, pattern, - op, - dom, identifier, word, - date, duration }; + enum class Type { + uuid, + number, + hex, + string, + url, + pair, + set, + separator, + tag, + path, + substitution, + pattern, + op, + dom, + identifier, + word, + date, + duration + }; - Lexer (const std::string&); - ~Lexer () = default; - bool token (std::string&, Lexer::Type&); - static std::vector split (const std::string&); - static std::string typeToString (Lexer::Type); + Lexer(const std::string&); + ~Lexer() = default; + bool token(std::string&, Lexer::Type&); + static std::vector split(const std::string&); + static std::string typeToString(Lexer::Type); // Static helpers. - static const std::string typeName (const Lexer::Type&); - static bool isIdentifierStart (int); - static bool isIdentifierNext (int); - static bool isSingleCharOperator (int); - static bool isDoubleCharOperator (int, int, int); - static bool isTripleCharOperator (int, int, int, int); - static bool isBoundary (int, int); - static bool isHardBoundary (int, int); - static bool isPunctuation (int); - static bool isAllDigits (const std::string&); - static bool isDOM (const std::string&); - static void dequote (std::string&, const std::string& quotes = "'\""); - static bool wasQuoted (const std::string&); - static bool readWord (const std::string&, const std::string&, std::string::size_type&, std::string&); - static bool readWord (const std::string&, std::string::size_type&, std::string&); - static bool decomposePair (const std::string&, std::string&, std::string&, std::string&, std::string&); - static bool decomposeSubstitution (const std::string&, std::string&, std::string&, std::string&); - static bool decomposePattern (const std::string&, std::string&, std::string&); - static int hexToInt (int); - static int hexToInt (int, int); - static int hexToInt (int, int, int, int); - static std::string::size_type commonLength (const std::string&, const std::string&); - static std::string::size_type commonLength (const std::string&, std::string::size_type, const std::string&, std::string::size_type); - static std::string commify (const std::string&); - static std::string lowerCase (const std::string&); - static std::string ucFirst (const std::string&); - static std::string trimLeft (const std::string& in, const std::string& t = " "); - static std::string trimRight (const std::string& in, const std::string& t = " "); - static std::string trim (const std::string& in, const std::string& t = " "); + static const std::string typeName(const Lexer::Type&); + static bool isIdentifierStart(int); + static bool isIdentifierNext(int); + static bool isSingleCharOperator(int); + static bool isDoubleCharOperator(int, int, int); + static bool isTripleCharOperator(int, int, int, int); + static bool isBoundary(int, int); + static bool isHardBoundary(int, int); + static bool isPunctuation(int); + static bool isAllDigits(const std::string&); + static bool isDOM(const std::string&); + static void dequote(std::string&, const std::string& quotes = "'\""); + static bool wasQuoted(const std::string&); + static bool readWord(const std::string&, const std::string&, std::string::size_type&, + std::string&); + static bool readWord(const std::string&, std::string::size_type&, std::string&); + static bool decomposePair(const std::string&, std::string&, std::string&, std::string&, + std::string&); + static bool decomposeSubstitution(const std::string&, std::string&, std::string&, std::string&); + static bool decomposePattern(const std::string&, std::string&, std::string&); + static int hexToInt(int); + static int hexToInt(int, int); + static int hexToInt(int, int, int, int); + static std::string::size_type commonLength(const std::string&, const std::string&); + static std::string::size_type commonLength(const std::string&, std::string::size_type, + const std::string&, std::string::size_type); + static std::string commify(const std::string&); + static std::string lowerCase(const std::string&); + static std::string ucFirst(const std::string&); + static std::string trimLeft(const std::string& in, const std::string& t = " "); + static std::string trimRight(const std::string& in, const std::string& t = " "); + static std::string trim(const std::string& in, const std::string& t = " "); // Stream Classifiers. - bool isEOS () const; - bool isString (std::string&, Lexer::Type&, const std::string&); - bool isDate (std::string&, Lexer::Type&); - bool isDuration (std::string&, Lexer::Type&); - bool isUUID (std::string&, Lexer::Type&, bool); - bool isNumber (std::string&, Lexer::Type&); - bool isInteger (std::string&, Lexer::Type&); - bool isHexNumber (std::string&, Lexer::Type&); - bool isSeparator (std::string&, Lexer::Type&); - bool isURL (std::string&, Lexer::Type&); - bool isPair (std::string&, Lexer::Type&); - bool isSet (std::string&, Lexer::Type&); - bool isTag (std::string&, Lexer::Type&); - bool isPath (std::string&, Lexer::Type&); - bool isSubstitution (std::string&, Lexer::Type&); - bool isPattern (std::string&, Lexer::Type&); - bool isOperator (std::string&, Lexer::Type&); - bool isDOM (std::string&, Lexer::Type&); - bool isIdentifier (std::string&, Lexer::Type&); - bool isWord (std::string&, Lexer::Type&); - bool isLiteral (const std::string&, bool, bool); - bool isOneOf (const std::vector &, bool, bool); - bool isOneOf (const std::map &, bool, bool); + bool isEOS() const; + bool isString(std::string&, Lexer::Type&, const std::string&); + bool isDate(std::string&, Lexer::Type&); + bool isDuration(std::string&, Lexer::Type&); + bool isUUID(std::string&, Lexer::Type&, bool); + bool isNumber(std::string&, Lexer::Type&); + bool isInteger(std::string&, Lexer::Type&); + bool isHexNumber(std::string&, Lexer::Type&); + bool isSeparator(std::string&, Lexer::Type&); + bool isURL(std::string&, Lexer::Type&); + bool isPair(std::string&, Lexer::Type&); + bool isSet(std::string&, Lexer::Type&); + bool isTag(std::string&, Lexer::Type&); + bool isPath(std::string&, Lexer::Type&); + bool isSubstitution(std::string&, Lexer::Type&); + bool isPattern(std::string&, Lexer::Type&); + bool isOperator(std::string&, Lexer::Type&); + bool isDOM(std::string&, Lexer::Type&); + bool isIdentifier(std::string&, Lexer::Type&); + bool isWord(std::string&, Lexer::Type&); + bool isLiteral(const std::string&, bool, bool); + bool isOneOf(const std::vector&, bool, bool); + bool isOneOf(const std::map&, bool, bool); -private: + private: std::string _text; std::size_t _cursor; std::size_t _eos; diff --git a/src/tc/util.cpp b/src/Operation.cpp similarity index 62% rename from src/tc/util.cpp rename to src/Operation.cpp index 567c4a05b..20796803b 100644 --- a/src/tc/util.cpp +++ b/src/Operation.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // -// Copyright 2022, Dustin J. Mitchell +// Copyright 2006 - 2024, Tomas Babej, Paul Beckingham, Federico Hernandez. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -25,57 +25,49 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include "tc/Replica.h" -#include "tc/Task.h" +// cmake.h include header must come first -using namespace tc::ffi; +#include +#include + +#include -namespace tc { //////////////////////////////////////////////////////////////////////////////// -TCString string2tc (const std::string& str) -{ - return tc_string_clone_with_len (str.data (), str.size ()); +Operation::Operation(const tc::Operation& op) : op(&op) {} + +//////////////////////////////////////////////////////////////////////////////// +std::vector Operation::operations(const rust::Vec& operations) { + return {operations.begin(), operations.end()}; } //////////////////////////////////////////////////////////////////////////////// -std::string tc2string_clone (const TCString& str) -{ - size_t len; - auto ptr = tc_string_content_with_len (&str, &len); - auto rv = std::string (ptr, len); - return rv; +Operation& Operation::operator=(const Operation& other) { + op = other.op; + return *this; } //////////////////////////////////////////////////////////////////////////////// -std::string tc2string (TCString& str) -{ - auto rv = tc2string_clone(str); - tc_string_free (&str); - return rv; -} - -//////////////////////////////////////////////////////////////////////////////// -TCUuid uuid2tc(const std::string& str) -{ - TCString tcstr = tc_string_borrow(str.c_str()); - TCUuid rv; - if (TC_RESULT_OK != tc_uuid_from_str(tcstr, &rv)) { - throw std::string ("invalid UUID"); +bool Operation::operator<(const Operation& other) const { + if (is_create()) { + return !other.is_create(); + } else if (is_update()) { + if (other.is_create()) { + return false; + } else if (other.is_update()) { + return get_timestamp() < other.get_timestamp(); + } else { + return true; + } + } else if (is_delete()) { + if (other.is_create() || other.is_update() || other.is_delete()) { + return false; + } else { + return true; + } + } else if (is_undo_point()) { + return !other.is_undo_point(); } - return rv; + return false; // not reachable } //////////////////////////////////////////////////////////////////////////////// -std::string tc2uuid (TCUuid& uuid) -{ - char s[TC_UUID_STRING_BYTES]; - tc_uuid_to_buf (uuid, s); - std::string str; - str.assign (s, TC_UUID_STRING_BYTES); - return str; -} - -//////////////////////////////////////////////////////////////////////////////// -} diff --git a/src/Operation.h b/src/Operation.h new file mode 100644 index 000000000..82c2ea022 --- /dev/null +++ b/src/Operation.h @@ -0,0 +1,89 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_OPERATION +#define INCLUDED_OPERATION + +#include + +#include +#include + +// Representation of a TaskChampion operation. +// +// This class wraps `tc::Operation&` and thus cannot outlive that underlying +// type. +class Operation { + public: + explicit Operation(const tc::Operation &); + + Operation(const Operation &other) = default; + Operation &operator=(const Operation &other); + + // Create a vector of Operations given the result of `Replica::get_undo_operations` or + // `Replica::get_task_operations`. The resulting vector must not outlive the input `rust::Vec`. + static std::vector operations(const rust::Vec &); + + // Methods from the underlying `tc::Operation`. + bool is_create() const { return op->is_create(); } + bool is_update() const { return op->is_update(); } + bool is_delete() const { return op->is_delete(); } + bool is_undo_point() const { return op->is_undo_point(); } + std::string get_uuid() const { return std::string(op->get_uuid().to_string()); } + ::rust::Vec<::tc::PropValuePair> get_old_task() const { return op->get_old_task(); }; + std::string get_property() const { + std::string value; + op->get_property(value); + return value; + } + std::optional get_value() const { + std::optional value{std::string()}; + if (!op->get_value(value.value())) { + value = std::nullopt; + } + return value; + } + std::optional get_old_value() const { + std::optional old_value{std::string()}; + if (!op->get_old_value(old_value.value())) { + old_value = std::nullopt; + } + return old_value; + } + time_t get_timestamp() const { return static_cast(op->get_timestamp()); } + + // Define a partial order on Operations: + // - Create < Update < Delete < UndoPoint + // - Given two updates, sort by timestamp + bool operator<(const Operation &other) const; + + private: + const tc::Operation *op; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/TDB2.cpp b/src/TDB2.cpp index 42b747be5..5676a509f 100644 --- a/src/TDB2.cpp +++ b/src/TDB2.cpp @@ -25,109 +25,71 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include +#include #include +#include #include -#include #include -#include +#include +#include #include -#include "tc/Server.h" -#include "tc/util.h" + +#include +#include +#include bool TDB2::debug_mode = false; -static void dependency_scan (std::vector &); +static void dependency_scan(std::vector&); //////////////////////////////////////////////////////////////////////////////// -TDB2::TDB2 () -: replica {tc::Replica()} // in-memory Replica -, _working_set {} -{ +void TDB2::open_replica(const std::string& location, bool create_if_missing, bool read_write) { + _replica = tc::new_replica_on_disk(location, create_if_missing, read_write); } //////////////////////////////////////////////////////////////////////////////// -void TDB2::open_replica (const std::string& location, bool create_if_missing) -{ - File pending_data = File (location + "/pending.data"); - if (pending_data.exists()) { - Color warning = Color (Context::getContext ().config.get ("color.warning")); - std::cerr << warning.colorize ( - format ("Found existing '.data' files in {1}", location)) << "\n"; - std::cerr << " Taskwarrior's storage format changed in 3.0, requiring a manual migration.\n"; - std::cerr << " See https://github.com/GothenburgBitFactory/taskwarrior/releases.\n"; - } - replica = tc::Replica(location, create_if_missing); -} +void TDB2::open_replica_in_memory() { _replica = tc::new_replica_in_memory(); } //////////////////////////////////////////////////////////////////////////////// // Add the new task to the replica. -void TDB2::add (Task& task) -{ +void TDB2::add(Task& task) { // Ensure the task is consistent, and provide defaults if necessary. // bool argument to validate() is "applyDefault", to apply default values for // properties not otherwise given. - task.validate (true); + task.validate(true); - std::string uuid = task.get ("uuid"); + rust::Vec ops; + maybe_add_undo_point(ops); + + auto uuid = task.get("uuid"); changes[uuid] = task; - - auto innertask = replica.import_task_with_uuid (uuid); - - { - auto guard = replica.mutate_task(innertask); - - // add the task attributes - for (auto& attr : task.all ()) { - // TaskChampion does not store uuid or id in the taskmap - if (attr == "uuid" || attr == "id") { - continue; - } - - // Use `set_status` for the task status, to get expected behavior - // with respect to the working set. - else if (attr == "status") { - innertask.set_status (Task::status2tc (Task::textToStatus (task.get (attr)))); - } - - // use `set_modified` to set the modified timestamp, avoiding automatic - // updates to this field by TaskChampion. - else if (attr == "modified") { - auto mod = (time_t) std::stoi (task.get (attr)); - innertask.set_modified (mod); - } - - // otherwise, just set the k/v map value - else { - innertask.set_value (attr, std::make_optional (task.get (attr))); - } - } - } - - auto ws = replica.working_set (); - - // get the ID that was assigned to this task - auto id = ws.by_uuid (uuid); - - // update the cached working set with the new information - _working_set = std::make_optional (std::move (ws)); + tc::Uuid tcuuid = tc::uuid_from_string(uuid); // run hooks for this new task - Context::getContext ().hooks.onAdd (task); + Context::getContext().hooks.onAdd(task); - if (id.has_value ()) { - task.id = id.value(); + auto taskdata = tc::create_task(tcuuid, ops); + + // add the task attributes + for (auto& attr : task.all()) { + // TaskChampion does not store uuid or id in the task data + if (attr == "uuid" || attr == "id") { + continue; + } + + taskdata->update(attr, task.get(attr), ops); } + replica()->commit_operations(std::move(ops)); + invalidate_cached_info(); + + // get the ID that was assigned to this task + auto id = working_set()->by_uuid(tcuuid); + if (id > 0) { + task.id = id; + } } //////////////////////////////////////////////////////////////////////////////// @@ -145,31 +107,34 @@ void TDB2::add (Task& task) // this method. In this case, this method throws an error that will make sense // to the user. This is especially unlikely since tasks are only deleted when // they have been unmodified for a long time. -void TDB2::modify (Task& task) -{ +void TDB2::modify(Task& task) { // All locally modified tasks are timestamped, implicitly overwriting any // changes the user or hooks tried to apply to the "modified" attribute. - task.setAsNow ("modified"); - task.validate (false); - auto uuid = task.get ("uuid"); + task.setAsNow("modified"); + task.validate(false); + auto uuid = task.get("uuid"); + + rust::Vec ops; + maybe_add_undo_point(ops); changes[uuid] = task; - // invoke the hook and allow it to modify the task before updating + // invoke the hook and allow it to modify the task before updating Task original; - get (uuid, original); - Context::getContext ().hooks.onModify (original, task); + get(uuid, original); + Context::getContext().hooks.onModify(original, task); - auto maybe_tctask = replica.get_task (uuid); - if (!maybe_tctask.has_value ()) { - throw std::string ("task no longer exists"); + tc::Uuid tcuuid = tc::uuid_from_string(uuid); + auto maybe_tctask = replica()->get_task_data(tcuuid); + if (maybe_tctask.is_none()) { + throw std::string("task no longer exists"); } - auto tctask = std::move (maybe_tctask.value ()); - auto guard = replica.mutate_task(tctask); - auto tctask_map = tctask.get_taskmap (); + auto tctask = maybe_tctask.take(); + // Perform the necessary `update` operations to set all keys in `tctask` + // equal to those in `task`. std::unordered_set seen; - for (auto k : task.all ()) { + for (auto k : task.all()) { // ignore task keys that aren't stored if (k == "uuid") { continue; @@ -177,43 +142,75 @@ void TDB2::modify (Task& task) seen.insert(k); bool update = false; auto v_new = task.get(k); - try { - auto v_tctask = tctask_map.at(k); + std::string v_tctask; + if (tctask->get(k, v_tctask)) { update = v_tctask != v_new; - } catch (const std::out_of_range& oor) { - // tctask_map does not contain k, so update it + } else { + // tctask does not contain k, so update it update = true; } if (update) { // An empty string indicates the value should be removed. if (v_new == "") { - tctask.set_value(k, {}); + tctask->update_remove(k, ops); } else { - tctask.set_value(k, make_optional (v_new)); + tctask->update(k, v_new, ops); } } } // we've now added and updated properties; but must find any deleted properties - for (auto kv : tctask_map) { - if (seen.find (kv.first) == seen.end ()) { - tctask.set_value (kv.first, {}); + for (auto k : tctask->properties()) { + auto kstr = static_cast(k); + if (seen.find(kstr) == seen.end()) { + tctask->update_remove(kstr, ops); } } + + replica()->commit_operations(std::move(ops)); + + invalidate_cached_info(); } //////////////////////////////////////////////////////////////////////////////// -const tc::WorkingSet &TDB2::working_set () -{ - if (!_working_set.has_value ()) { - _working_set = std::make_optional (replica.working_set ()); +void TDB2::purge(Task& task) { + auto uuid = tc::uuid_from_string(task.get("uuid")); + rust::Vec ops; + auto maybe_tctask = replica()->get_task_data(uuid); + if (maybe_tctask.is_some()) { + auto tctask = maybe_tctask.take(); + tctask->delete_task(ops); + replica()->commit_operations(std::move(ops)); } - return _working_set.value (); + + invalidate_cached_info(); } //////////////////////////////////////////////////////////////////////////////// -void TDB2::get_changes (std::vector & changes) -{ +rust::Box& TDB2::replica() { + // One of the open_replica_ methods must be called before this one. + assert(_replica); + return _replica.value(); +} + +//////////////////////////////////////////////////////////////////////////////// +const rust::Box& TDB2::working_set() { + if (!_working_set.has_value()) { + _working_set = replica()->working_set(); + } + return _working_set.value(); +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB2::maybe_add_undo_point(rust::Vec& ops) { + // Only add an UndoPoint if there are not yet any changes. + if (changes.size() == 0) { + tc::add_undo_point(ops); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void TDB2::get_changes(std::vector& changes) { std::map& changes_map = this->changes; changes.clear(); std::transform(changes_map.begin(), changes_map.end(), std::back_inserter(changes), @@ -221,178 +218,105 @@ void TDB2::get_changes (std::vector & changes) } //////////////////////////////////////////////////////////////////////////////// -void TDB2::revert () -{ - auto undo_ops = replica.get_undo_ops(); - if (undo_ops.len == 0) { - std::cout << "No operations to undo."; - return; - } - if (confirm_revert(undo_ops)) { - // Has the side-effect of freeing undo_ops. - replica.commit_undo_ops(undo_ops, NULL); - } else { - replica.free_replica_ops(undo_ops); - } - replica.rebuild_working_set (false); -} - -//////////////////////////////////////////////////////////////////////////////// -bool TDB2::confirm_revert (struct tc::ffi::TCReplicaOpList undo_ops) -{ - // TODO Use show_diff rather than this basic listing of operations, though - // this might be a worthy undo.style itself. - std::cout << "The following " << undo_ops.len << " operations would be reverted:\n"; - for (size_t i = 0; i < undo_ops.len; i++) { - std::cout << "- "; - tc::ffi::TCReplicaOp op = undo_ops.items[i]; - switch(op.operation_type) { - case tc::ffi::TCReplicaOpType::Create: - std::cout << "Create " << replica.get_op_uuid(op); - break; - case tc::ffi::TCReplicaOpType::Delete: - std::cout << "Delete " << replica.get_op_old_task_description(op); - break; - case tc::ffi::TCReplicaOpType::Update: - std::cout << "Update " << replica.get_op_uuid(op) << "\n"; - std::cout << " " << replica.get_op_property(op) << ": " << option_string(replica.get_op_old_value(op)) << " -> " << option_string(replica.get_op_value(op)); - break; - case tc::ffi::TCReplicaOpType::UndoPoint: - throw std::string ("Can't undo UndoPoint."); - break; - default: - throw std::string ("Can't undo non-operation."); - break; - } - std::cout << "\n"; - } - return ! Context::getContext ().config.getBoolean ("confirmation") || - confirm ("The undo command is not reversible. Are you sure you want to revert to the previous state?"); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string TDB2::option_string(std::string input) { - return input == "" ? "" : input; -} - -//////////////////////////////////////////////////////////////////////////////// -void TDB2::show_diff ( - const std::string& current, - const std::string& prior, - const std::string& when) -{ - Datetime lastChange (strtoll (when.c_str (), nullptr, 10)); - - // Set the colors. - Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : ""); - Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : ""); - - auto before = prior == "" ? Task() : Task(prior); - auto after = Task(current); - - if (Context::getContext ().config.get ("undo.style") == "side") - { - Table view = before.diffForUndoSide(after); - - std::cout << '\n' - << format ("The last modification was made {1}", lastChange.toString ()) - << '\n' - << '\n' - << view.render () - << '\n'; - } - - else if (Context::getContext ().config.get ("undo.style") == "diff") - { - Table view = before.diffForUndoPatch(after, lastChange); - std::cout << '\n' - << view.render () - << '\n'; - } -} - -void TDB2::gc () -{ +void TDB2::gc() { Timer timer; // Allowed as an override, but not recommended. - if (Context::getContext ().config.getBoolean ("gc")) - { - replica.rebuild_working_set (true); + if (Context::getContext().config.getBoolean("gc")) { + replica()->rebuild_working_set(true); } - Context::getContext ().time_gc_us += timer.total_us (); + Context::getContext().time_gc_us += timer.total_us(); } +//////////////////////////////////////////////////////////////////////////////// +void TDB2::expire_tasks() { replica()->expire_tasks(); } + //////////////////////////////////////////////////////////////////////////////// // Latest ID is that of the last pending task. -int TDB2::latest_id () -{ - const tc::WorkingSet &ws = working_set (); - return (int)ws.largest_index (); +int TDB2::latest_id() { + auto& ws = working_set(); + return (int)ws->largest_index(); } //////////////////////////////////////////////////////////////////////////////// -const std::vector TDB2::all_tasks () -{ - auto all_tctasks = replica.all_tasks(); - std::vector all; - for (auto& tctask : all_tctasks) - all.push_back (Task (std::move (tctask))); +const std::vector TDB2::all_tasks() { + Timer timer; + auto all_tctasks = replica()->all_task_data(); + std::vector all; + for (auto& maybe_tctask : all_tctasks) { + auto tctask = maybe_tctask.take(); + all.push_back(Task(std::move(tctask))); + } + dependency_scan(all); + + Context::getContext().time_load_us += timer.total_us(); return all; } //////////////////////////////////////////////////////////////////////////////// -const std::vector TDB2::pending_tasks () -{ - const tc::WorkingSet &ws = working_set (); - auto largest_index = ws.largest_index (); +const std::vector TDB2::pending_tasks() { + if (!_pending_tasks) { + Timer timer; - std::vector result; - for (size_t i = 0; i <= largest_index; i++) { - auto maybe_uuid = ws.by_index (i); - if (maybe_uuid.has_value ()) { - auto maybe_task = replica.get_task (maybe_uuid.value ()); - if (maybe_task.has_value ()) { - result.push_back (Task (std::move (maybe_task.value ()))); - } + auto pending_tctasks = replica()->pending_task_data(); + std::vector result; + for (auto& maybe_tctask : pending_tctasks) { + auto tctask = maybe_tctask.take(); + result.push_back(Task(std::move(tctask))); } + + dependency_scan(result); + + Context::getContext().time_load_us += timer.total_us(); + _pending_tasks = result; } - dependency_scan(result); - - return result; + return *_pending_tasks; } //////////////////////////////////////////////////////////////////////////////// -const std::vector TDB2::completed_tasks () -{ - auto all_tctasks = replica.all_tasks(); - const tc::WorkingSet &ws = working_set (); +const std::vector TDB2::completed_tasks() { + if (!_completed_tasks) { + auto all_tctasks = replica()->all_task_data(); + auto& ws = working_set(); - std::vector result; - for (auto& tctask : all_tctasks) { - // if this task is _not_ in the working set, return it. - if (!ws.by_uuid (tctask.get_uuid ())) { - result.push_back (Task (std::move (tctask))); + std::vector result; + for (auto& maybe_tctask : all_tctasks) { + auto tctask = maybe_tctask.take(); + // if this task is _not_ in the working set, return it. + if (ws->by_uuid(tctask->get_uuid()) == 0) { + result.push_back(Task(std::move(tctask))); + } } + _completed_tasks = result; } + return *_completed_tasks; +} - return result; +//////////////////////////////////////////////////////////////////////////////// +void TDB2::invalidate_cached_info() { + _pending_tasks = std::nullopt; + _completed_tasks = std::nullopt; + _working_set = std::nullopt; } //////////////////////////////////////////////////////////////////////////////// // Locate task by ID, wherever it is. -bool TDB2::get (int id, Task& task) -{ - const tc::WorkingSet &ws = working_set (); - const auto maybe_uuid = ws.by_index (id); - if (maybe_uuid) { - auto maybe_task = replica.get_task(*maybe_uuid); - if (maybe_task) { - task = Task{std::move(*maybe_task)}; - return true; +bool TDB2::get(int id, Task& task) { + auto& ws = working_set(); + const auto tcuuid = ws->by_index(id); + if (!tcuuid.is_nil()) { + std::string uuid = static_cast(tcuuid.to_string()); + // Load all pending tasks in order to get dependency data, and in particular + // `task.is_blocking` and `task.is_blocked`, set correctly. + std::vector pending = pending_tasks(); + for (auto& pending_task : pending) { + if (pending_task.get("uuid") == uuid) { + task = pending_task; + return true; + } } } @@ -401,25 +325,25 @@ bool TDB2::get (int id, Task& task) //////////////////////////////////////////////////////////////////////////////// // Locate task by UUID, including by partial ID, wherever it is. -bool TDB2::get (const std::string& uuid, Task& task) -{ +bool TDB2::get(const std::string& uuid, Task& task) { + // Load all pending tasks in order to get dependency data, and in particular + // `task.is_blocking` and `task.is_blocked`, set correctly. + std::vector pending = pending_tasks(); + // try by raw uuid, if the length is right - if (uuid.size () == 36) { - try { - auto maybe_task = replica.get_task (uuid); - if (maybe_task) { - task = Task{std::move (*maybe_task)}; - return true; - } - } catch (const std::string &err) { - return false; + for (auto& pending_task : pending) { + if (closeEnough(pending_task.get("uuid"), uuid, uuid.length())) { + task = pending_task; + return true; } } - // Nothing to do but iterate over all tasks and check whether it's closeEnough - for (auto& tctask : replica.all_tasks ()) { - if (closeEnough (tctask.get_uuid (), uuid, uuid.length ())) { - task = Task{std::move (tctask)}; + // Nothing to do but iterate over all tasks and check whether it's closeEnough. + for (auto& maybe_tctask : replica()->all_task_data()) { + auto tctask = maybe_tctask.take(); + auto tctask_uuid = static_cast(tctask->get_uuid().to_string()); + if (closeEnough(tctask_uuid, uuid, uuid.length())) { + task = Task{std::move(tctask)}; return true; } } @@ -429,34 +353,24 @@ bool TDB2::get (const std::string& uuid, Task& task) //////////////////////////////////////////////////////////////////////////////// // Locate task by UUID, wherever it is. -bool TDB2::has (const std::string& uuid) -{ - Task task; - return get(uuid, task); +bool TDB2::has(const std::string& uuid) { + return replica()->get_task_data(tc::uuid_from_string(uuid)).is_some(); } //////////////////////////////////////////////////////////////////////////////// -const std::vector TDB2::siblings (Task& task) -{ - std::vector results; - if (task.has ("parent")) - { - std::string parent = task.get ("parent"); +const std::vector TDB2::siblings(Task& task) { + std::vector results; + if (task.has("parent")) { + std::string parent = task.get("parent"); - for (auto& i : this->pending_tasks()) - { + for (auto& i : this->pending_tasks()) { // Do not include self in results. - if (i.id != task.id) - { + if (i.id != task.id) { // Do not include completed or deleted tasks. - if (i.getStatus () != Task::completed && - i.getStatus () != Task::deleted) - { + if (i.getStatus() != Task::completed && i.getStatus() != Task::deleted) { // If task has the same parent, it is a sibling. - if (i.has ("parent") && - i.get ("parent") == parent) - { - results.push_back (i); + if (i.has("parent") && i.get("parent") == parent) { + results.push_back(i); } } } @@ -467,107 +381,79 @@ const std::vector TDB2::siblings (Task& task) } //////////////////////////////////////////////////////////////////////////////// -const std::vector TDB2::children (Task& parent) -{ +const std::vector TDB2::children(Task& parent) { // scan _pending_ tasks for those with `parent` equal to this task - std::vector results; - std::string this_uuid = parent.get ("uuid"); + std::vector results; + std::string this_uuid = parent.get("uuid"); - const tc::WorkingSet &ws = working_set (); - size_t end_idx = ws.largest_index (); + auto& ws = working_set(); + size_t end_idx = ws->largest_index(); for (size_t i = 0; i <= end_idx; i++) { - auto uuid_opt = ws.by_index (i); - if (!uuid_opt) { + auto uuid = ws->by_index(i); + if (uuid.is_nil()) { continue; } - auto uuid = uuid_opt.value (); // skip self-references - if (uuid == this_uuid) { + if (uuid.to_string() == this_uuid) { continue; } - auto task_opt = replica.get_task (uuid_opt.value ()); - if (!task_opt) { + auto task_opt = replica()->get_task_data(uuid); + if (task_opt.is_none()) { continue; } - auto task = std::move (task_opt.value ()); + auto task = task_opt.take(); - auto parent_uuid_opt = task.get_value ("parent"); - if (!parent_uuid_opt) { + std::string parent_uuid; + if (!task->get("parent", parent_uuid)) { continue; } - auto parent_uuid = parent_uuid_opt.value (); if (parent_uuid == this_uuid) { - results.push_back (Task (std::move (task))); + results.push_back(Task(std::move(task))); } } - return results; } //////////////////////////////////////////////////////////////////////////////// -std::string TDB2::uuid (int id) -{ - const tc::WorkingSet &ws = working_set (); - return ws.by_index ((size_t)id).value_or (""); +std::string TDB2::uuid(int id) { + auto& ws = working_set(); + auto uuid = ws->by_index(id); + if (uuid.is_nil()) { + return ""; + } + return static_cast(uuid.to_string()); } //////////////////////////////////////////////////////////////////////////////// -int TDB2::id (const std::string& uuid) -{ - const tc::WorkingSet &ws = working_set (); - return (int)ws.by_uuid (uuid).value_or (0); +int TDB2::id(const std::string& uuid) { + auto& ws = working_set(); + return ws->by_uuid(tc::uuid_from_string(uuid)); } //////////////////////////////////////////////////////////////////////////////// -int TDB2::num_local_changes () -{ - return (int)replica.num_local_operations (); -} +int TDB2::num_local_changes() { return (int)replica()->num_local_operations(); } //////////////////////////////////////////////////////////////////////////////// -int TDB2::num_reverts_possible () -{ - return (int)replica.num_undo_points (); -} - -//////////////////////////////////////////////////////////////////////////////// -void TDB2::sync (tc::Server server, bool avoid_snapshots) -{ - replica.sync(std::move(server), avoid_snapshots); -} - -//////////////////////////////////////////////////////////////////////////////// -void TDB2::dump () -{ - // TODO -} +int TDB2::num_reverts_possible() { return (int)replica()->num_undo_points(); } //////////////////////////////////////////////////////////////////////////////// // For any task that has depenencies, follow the chain of dependencies until the // end. Along the way, update the Task::is_blocked and Task::is_blocking data // cache. -static void dependency_scan (std::vector &tasks) -{ - for (auto& left : tasks) - { - for (auto& dep : left.getDependencyUUIDs ()) - { - for (auto& right : tasks) - { - if (right.get ("uuid") == dep) - { +static void dependency_scan(std::vector& tasks) { + for (auto& left : tasks) { + for (auto& dep : left.getDependencyUUIDs()) { + for (auto& right : tasks) { + if (right.get("uuid") == dep) { // GC hasn't run yet, check both tasks for their current status - Task::status lstatus = left.getStatus (); - Task::status rstatus = right.getStatus (); - if (lstatus != Task::completed && - lstatus != Task::deleted && - rstatus != Task::completed && - rstatus != Task::deleted) - { + Task::status lstatus = left.getStatus(); + Task::status rstatus = right.getStatus(); + if (lstatus != Task::completed && lstatus != Task::deleted && + rstatus != Task::completed && rstatus != Task::deleted) { left.is_blocked = true; right.is_blocking = true; } diff --git a/src/TDB2.h b/src/TDB2.h index 5716ec844..51669ab6d 100644 --- a/src/TDB2.h +++ b/src/TDB2.h @@ -27,69 +27,66 @@ #ifndef INCLUDED_TDB2 #define INCLUDED_TDB2 -#include -#include -#include -#include -#include -#include -#include #include -#include -#include +#include -namespace tc { -class Server; -} +#include +#include +#include +#include +#include +#include // TDB2 Class represents all the files in the task database. -class TDB2 -{ -public: +class TDB2 { + public: static bool debug_mode; - TDB2 (); + TDB2() = default; - void open_replica (const std::string&, bool create_if_missing); - void add (Task&); - void modify (Task&); - void get_changes (std::vector &); - void revert (); - void gc (); - int latest_id (); + void open_replica(const std::string &, bool create_if_missing, bool read_write); + void open_replica_in_memory(); + void add(Task &); + void modify(Task &); + void purge(Task &); + void get_changes(std::vector &); + void gc(); + void expire_tasks(); + int latest_id(); // Generalized task accessors. - const std::vector all_tasks (); - const std::vector pending_tasks (); - const std::vector completed_tasks (); - bool get (int, Task&); - bool get (const std::string&, Task&); - bool has (const std::string&); - const std::vector siblings (Task&); - const std::vector children (Task&); + const std::vector all_tasks(); + const std::vector pending_tasks(); + const std::vector completed_tasks(); + bool get(int, Task &); + bool get(const std::string &, Task &); + bool has(const std::string &); + const std::vector siblings(Task &); + const std::vector children(Task &); // ID <--> UUID mapping. - std::string uuid (int); - int id (const std::string&); + std::string uuid(int); + int id(const std::string &); - int num_local_changes (); - int num_reverts_possible (); + int num_local_changes(); + int num_reverts_possible(); - void dump (); + rust::Box &replica(); - void sync (tc::Server server, bool avoid_snapshots); - bool confirm_revert(struct tc::ffi::TCReplicaOpList); + private: + std::optional> _replica; -private: - tc::Replica replica; - std::optional _working_set; + // Cached information from the replica + std::optional> _working_set; + std::optional> _pending_tasks; + std::optional> _completed_tasks; + void invalidate_cached_info(); // UUID -> Task containing all tasks modified in this invocation. std::map changes; - const tc::WorkingSet &working_set (); - static std::string option_string (std::string input); - static void show_diff (const std::string&, const std::string&, const std::string&); + const rust::Box &working_set(); + void maybe_add_undo_point(rust::Vec &); }; #endif diff --git a/src/TF2.cpp b/src/TF2.cpp new file mode 100644 index 000000000..0b78fe531 --- /dev/null +++ b/src/TF2.cpp @@ -0,0 +1,178 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#ifdef PRODUCT_TASKWARRIOR +#include +#endif +#include +#include + +#define STRING_TDB2_REVERTED "Modified task reverted." + +//////////////////////////////////////////////////////////////////////////////// +TF2::TF2() : _loaded_tasks(false), _loaded_lines(false) {} + +//////////////////////////////////////////////////////////////////////////////// +TF2::~TF2() {} + +//////////////////////////////////////////////////////////////////////////////// +void TF2::target(const std::string& f) { _file = File(f); } + +//////////////////////////////////////////////////////////////////////////////// +const std::vector>& TF2::get_tasks() { + if (!_loaded_tasks) load_tasks(); + + return _tasks; +} + +//////////////////////////////////////////////////////////////////////////////// +// Attempt an FF4 parse. +// +// Note that FF1, FF2, FF3, and JSON are no longer supported. +// +// start --> [ --> Att --> ] --> end +// ^ | +// +-------+ +// +std::map TF2::load_task(const std::string& input) { + std::map data; + + // File format version 4, from 2009-5-16 - now, v1.7.1+ + // This is the parse format tried first, because it is most used. + data.clear(); + + if (input[0] == '[') { + // Not using Pig to parse here (which would be idiomatic), because we + // don't need to differentiate betwen utf-8 and normal characters. + // Pig's scanning the string can be expensive. + auto ending_bracket = input.find_last_of(']'); + if (ending_bracket != std::string::npos) { + std::string line = input.substr(1, ending_bracket); + + if (line.length() == 0) throw std::string("Empty record in input."); + + Pig attLine(line); + std::string name; + std::string value; + while (!attLine.eos()) { + if (attLine.getUntilAscii(':', name) && attLine.skip(':') && + attLine.getQuoted('"', value)) { +#ifdef PRODUCT_TASKWARRIOR + legacyAttributeMap(name); +#endif + + data[name] = decode(json::decode(value)); + } + + attLine.skip(' '); + } + + std::string remainder; + attLine.getRemainder(remainder); + if (remainder.length()) throw std::string("Unrecognized characters at end of line."); + } + } else { + throw std::string("Record not recognized as format 4."); + } + + // for compatibility, include all tags in `tags` as `tag_..` attributes + if (data.find("tags") != data.end()) { + for (auto& tag : split(data["tags"], ',')) { + data[Task::tag2Attr(tag)] = "x"; + } + } + + // same for `depends` / `dep_..` + if (data.find("depends") != data.end()) { + for (auto& dep : split(data["depends"], ',')) { + data[Task::dep2Attr(dep)] = "x"; + } + } + + return data; +} + +//////////////////////////////////////////////////////////////////////////////// +// Decode values after parse. +// [ <- &open; +// ] <- &close; +const std::string TF2::decode(const std::string& value) const { + if (value.find('&') == std::string::npos) return value; + + auto modified = str_replace(value, "&open;", "["); + return str_replace(modified, "&close;", "]"); +} + +//////////////////////////////////////////////////////////////////////////////// +void TF2::load_tasks() { + Timer timer; + + if (!_loaded_lines) { + load_lines(); + } + + // Reduce unnecessary allocations/copies. + // Calling it on _tasks is the right thing to do even when from_gc is set. + _tasks.reserve(_lines.size()); + + int line_number = 0; // Used for error message in catch block. + try { + for (auto& line : _lines) { + ++line_number; + auto task = load_task(line); + _tasks.push_back(task); + } + + _loaded_tasks = true; + } + + catch (const std::string& e) { + throw e + format(" in {1} at line {2}", _file._data, line_number); + } + + Context::getContext().time_load_us += timer.total_us(); +} + +//////////////////////////////////////////////////////////////////////////////// +void TF2::load_lines() { + if (_file.open()) { + if (Context::getContext().config.getBoolean("locking")) _file.lock(); + + _file.read(_lines); + _file.close(); + _loaded_lines = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// vim: ts=2 et sw=2 diff --git a/src/TF2.h b/src/TF2.h new file mode 100644 index 000000000..14f0f2485 --- /dev/null +++ b/src/TF2.h @@ -0,0 +1,63 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2024, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_TF2 +#define INCLUDED_TF2 + +#include +#include + +#include +#include +#include + +// TF2 Class represents a single 2.x-style file in the task database. +// +// This is only used for importing tasks from 2.x. It only reads format 4, based +// on a stripped-down version of the TF2 class from v2.6.2. +class TF2 { + public: + TF2(); + ~TF2(); + + void target(const std::string&); + + const std::vector>& get_tasks(); + + std::map load_task(const std::string&); + void load_tasks(); + void load_lines(); + const std::string decode(const std::string& value) const; + + bool _loaded_tasks; + bool _loaded_lines; + std::vector> _tasks; + std::vector _lines; + File _file; +}; + +#endif +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Task.cpp b/src/Task.cpp index 2ba0b6d18..6608db850 100644 --- a/src/Task.cpp +++ b/src/Task.cpp @@ -25,67 +25,70 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include +#include + +#include #include #ifdef PRODUCT_TASKWARRIOR -#include #include +#include #endif -#include -#include #include + +#include +#include #ifdef PRODUCT_TASKWARRIOR #include #include #endif -#include #include +#include #ifdef PRODUCT_TASKWARRIOR #include #endif -#include #include +#include #include #ifdef PRODUCT_TASKWARRIOR -#include - #include -#include #include +#include +#include +#include - -#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery. +#define APPROACHING_INFINITY 1000 // Close enough. This isn't rocket surgery. static const float epsilon = 0.000001; #endif -std::string Task::defaultProject = ""; -std::string Task::defaultDue = ""; +std::string Task::defaultProject = ""; +std::string Task::defaultDue = ""; std::string Task::defaultScheduled = ""; -bool Task::searchCaseSensitive = true; -bool Task::regex = false; -std::map Task::attributes; +bool Task::searchCaseSensitive = true; +bool Task::regex = false; +std::map Task::attributes; -std::map Task::coefficients; -float Task::urgencyProjectCoefficient = 0.0; -float Task::urgencyActiveCoefficient = 0.0; -float Task::urgencyScheduledCoefficient = 0.0; -float Task::urgencyWaitingCoefficient = 0.0; -float Task::urgencyBlockedCoefficient = 0.0; +std::map Task::coefficients; +float Task::urgencyProjectCoefficient = 0.0; +float Task::urgencyActiveCoefficient = 0.0; +float Task::urgencyScheduledCoefficient = 0.0; +float Task::urgencyWaitingCoefficient = 0.0; +float Task::urgencyBlockedCoefficient = 0.0; float Task::urgencyAnnotationsCoefficient = 0.0; -float Task::urgencyTagsCoefficient = 0.0; -float Task::urgencyDueCoefficient = 0.0; -float Task::urgencyBlockingCoefficient = 0.0; -float Task::urgencyAgeCoefficient = 0.0; -float Task::urgencyAgeMax = 0.0; +float Task::urgencyTagsCoefficient = 0.0; +float Task::urgencyDueCoefficient = 0.0; +float Task::urgencyBlockingCoefficient = 0.0; +float Task::urgencyAgeCoefficient = 0.0; +float Task::urgencyAgeMax = 0.0; -std::map > Task::customOrder; +std::map> Task::customOrder; -static const std::string dummy (""); +static const std::string dummy(""); //////////////////////////////////////////////////////////////////////////////// // The uuid and id attributes must be exempt from comparison. @@ -99,86 +102,84 @@ static const std::string dummy (""); // These two conditions are necessary. They are also sufficient, since there // can be no extra data attribute in the second set, due to the same attribute // set sizes. -bool Task::operator== (const Task& other) -{ - if (data.size () != other.data.size ()) - return false; +bool Task::operator==(const Task& other) { + if (data.size() != other.data.size()) return false; for (const auto& i : data) - if (i.first != "uuid" && - i.second != other.get (i.first)) - return false; + if (i.first != "uuid" && i.second != other.get(i.first)) return false; return true; } //////////////////////////////////////////////////////////////////////////////// -bool Task::operator!= (const Task& other) -{ - return !(*this == other); -} +bool Task::operator!=(const Task& other) { return !(*this == other); } //////////////////////////////////////////////////////////////////////////////// -Task::Task (const std::string& input) -{ - id = 0; - urgency_value = 0.0; - recalc_urgency = true; - is_blocked = false; - is_blocking = false; +Task::Task(const std::string& input) { + id = 0; + urgency_value = 0.0; + recalc_urgency = true; + is_blocked = false; + is_blocking = false; annotation_count = 0; - parse (input); + parse(input); } //////////////////////////////////////////////////////////////////////////////// -Task::Task (const json::object* obj) -{ - id = 0; - urgency_value = 0.0; - recalc_urgency = true; - is_blocked = false; - is_blocking = false; +Task::Task(const json::object* obj) { + id = 0; + urgency_value = 0.0; + recalc_urgency = true; + is_blocked = false; + is_blocking = false; annotation_count = 0; - parseJSON (obj); + parseJSON(obj); } //////////////////////////////////////////////////////////////////////////////// -Task::Task (tc::Task obj) -{ - id = 0; - urgency_value = 0.0; - recalc_urgency = true; - is_blocked = false; - is_blocking = false; +Task::Task(rust::Box obj) { + id = 0; + urgency_value = 0.0; + recalc_urgency = true; + is_blocked = false; + is_blocking = false; annotation_count = 0; - parseTC (obj); + parseTC(std::move(obj)); } //////////////////////////////////////////////////////////////////////////////// -Task::status Task::textToStatus (const std::string& input) -{ - if (input[0] == 'p') return Task::pending; - else if (input[0] == 'c') return Task::completed; - else if (input[0] == 'd') return Task::deleted; - else if (input[0] == 'r') return Task::recurring; +Task::status Task::textToStatus(const std::string& input) { + if (input[0] == 'p') + return Task::pending; + else if (input[0] == 'c') + return Task::completed; + else if (input[0] == 'd') + return Task::deleted; + else if (input[0] == 'r') + return Task::recurring; // for compatibility, parse `w` as pending; Task::getStatus will // apply the virtual waiting status if appropriate - else if (input[0] == 'w') return Task::pending; + else if (input[0] == 'w') + return Task::pending; - throw format ("The status '{1}' is not valid.", input); + throw format("The status '{1}' is not valid.", input); } //////////////////////////////////////////////////////////////////////////////// -std::string Task::statusToText (Task::status s) -{ - if (s == Task::pending) return "pending"; - else if (s == Task::recurring) return "recurring"; - else if (s == Task::waiting) return "waiting"; - else if (s == Task::completed) return "completed"; - else if (s == Task::deleted) return "deleted"; +std::string Task::statusToText(Task::status s) { + if (s == Task::pending) + return "pending"; + else if (s == Task::recurring) + return "recurring"; + else if (s == Task::waiting) + return "waiting"; + else if (s == Task::completed) + return "completed"; + else if (s == Task::deleted) + return "deleted"; return "pending"; } @@ -186,161 +187,133 @@ std::string Task::statusToText (Task::status s) //////////////////////////////////////////////////////////////////////////////// // Returns a proper handle to the task. Tasks should not be referenced by UUIDs // as long as they have non-zero ID. -const std::string Task::identifier (bool shortened /* = false */) const -{ +const std::string Task::identifier(bool shortened /* = false */) const { if (id != 0) - return format (id); + return format(id); else if (shortened) - return get ("uuid").substr (0, 8); + return get("uuid").substr(0, 8); else - return get ("uuid"); + return get("uuid"); } //////////////////////////////////////////////////////////////////////////////// -void Task::setAsNow (const std::string& att) -{ +void Task::setAsNow(const std::string& att) { char now[22]; - snprintf (now, 22, "%lli", (long long int) time (nullptr)); - set (att, now); + snprintf(now, 22, "%lli", (long long int)time(nullptr)); + set(att, now); recalc_urgency = true; } //////////////////////////////////////////////////////////////////////////////// -bool Task::has (const std::string& name) const -{ - if (data.find (name) != data.end ()) - return true; +bool Task::has(const std::string& name) const { + if (data.find(name) != data.end()) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::all () const -{ - std::vector all; - for (const auto& i : data) - all.push_back (i.first); +std::vector Task::all() const { + std::vector all; + for (const auto& i : data) all.push_back(i.first); return all; } //////////////////////////////////////////////////////////////////////////////// -const std::string Task::get (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return i->second; +const std::string Task::get(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return i->second; return ""; } //////////////////////////////////////////////////////////////////////////////// -const std::string& Task::get_ref (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return i->second; +const std::string& Task::get_ref(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return i->second; return dummy; } //////////////////////////////////////////////////////////////////////////////// -int Task::get_int (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return strtol (i->second.c_str (), nullptr, 10); +int Task::get_int(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return strtol(i->second.c_str(), nullptr, 10); return 0; } //////////////////////////////////////////////////////////////////////////////// -unsigned long Task::get_ulong (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return strtoul (i->second.c_str (), nullptr, 10); +unsigned long Task::get_ulong(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return strtoul(i->second.c_str(), nullptr, 10); return 0; } //////////////////////////////////////////////////////////////////////////////// -float Task::get_float (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return strtof (i->second.c_str (), nullptr); +float Task::get_float(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return strtof(i->second.c_str(), nullptr); return 0.0; } //////////////////////////////////////////////////////////////////////////////// -time_t Task::get_date (const std::string& name) const -{ - auto i = data.find (name); - if (i != data.end ()) - return (time_t) strtoul (i->second.c_str (), nullptr, 10); +time_t Task::get_date(const std::string& name) const { + auto i = data.find(name); + if (i != data.end()) return (time_t)strtoul(i->second.c_str(), nullptr, 10); return 0; } //////////////////////////////////////////////////////////////////////////////// -void Task::set (const std::string& name, const std::string& value) -{ +void Task::set(const std::string& name, const std::string& value) { data[name] = value; - if (isAnnotationAttr (name)) - ++annotation_count; + if (isAnnotationAttr(name)) ++annotation_count; recalc_urgency = true; } //////////////////////////////////////////////////////////////////////////////// -void Task::set (const std::string& name, long long value) -{ - data[name] = format (value); +void Task::set(const std::string& name, long long value) { + data[name] = format(value); recalc_urgency = true; } //////////////////////////////////////////////////////////////////////////////// -void Task::remove (const std::string& name) -{ - if (data.erase (name)) - recalc_urgency = true; +void Task::remove(const std::string& name) { + if (data.erase(name)) recalc_urgency = true; - if (isAnnotationAttr (name)) - --annotation_count; + if (isAnnotationAttr(name)) --annotation_count; } //////////////////////////////////////////////////////////////////////////////// -Task::status Task::getStatus () const -{ - if (! has ("status")) - return Task::pending; +Task::status Task::getStatus() const { + if (!has("status")) return Task::pending; - auto status = textToStatus (get ("status")); + auto status = textToStatus(get("status")); // Implement the "virtual" Task::waiting status, which is not stored on-disk // but is defined as a pending task with a `wait` attribute in the future. // This is workaround for 2.6.0, remove in 3.0.0. - if (status == Task::pending && is_waiting ()) { - return Task::waiting; + if (status == Task::pending && is_waiting()) { + return Task::waiting; } return status; } //////////////////////////////////////////////////////////////////////////////// -void Task::setStatus (Task::status status) -{ +void Task::setStatus(Task::status status) { // the 'waiting' status is a virtual version of 'pending', so translate // that back to 'pending' here - if (status == Task::waiting) - status = Task::pending; + if (status == Task::waiting) status = Task::pending; - set ("status", statusToText (status)); + set("status", statusToText(status)); recalc_urgency = true; } @@ -348,33 +321,27 @@ void Task::setStatus (Task::status status) #ifdef PRODUCT_TASKWARRIOR //////////////////////////////////////////////////////////////////////////////// // Determines status of a date attribute. -Task::dateState Task::getDateState (const std::string& name) const -{ - std::string value = get (name); - if (value.length ()) - { - Datetime reference (value); +Task::dateState Task::getDateState(const std::string& name) const { + time_t value = get_date(name); + if (value > 0) { + Datetime reference(value); Datetime now; - Datetime today ("today"); + Datetime today("today"); - if (reference < today) - return dateBeforeToday; + if (reference < today) return dateBeforeToday; - if (reference.sameDay (now)) - { + if (reference.sameDay(now)) { if (reference < now) return dateEarlierToday; else return dateLaterToday; } - int imminentperiod = Context::getContext ().config.getInteger ("due"); - if (imminentperiod == 0) - return dateAfterToday; + int imminentperiod = Context::getContext().config.getInteger("due"); + if (imminentperiod == 0) return dateAfterToday; Datetime imminentDay = today + imminentperiod * 86400; - if (reference < imminentDay) - return dateAfterToday; + if (reference < imminentDay) return dateAfterToday; } return dateNotDue; @@ -383,36 +350,24 @@ Task::dateState Task::getDateState (const std::string& name) const //////////////////////////////////////////////////////////////////////////////// // An empty task is typically a "dummy", such as in DOM evaluation, which may or // may not occur in the context of a task. -bool Task::is_empty () const -{ - return data.size () == 0; -} +bool Task::is_empty() const { return data.size() == 0; } //////////////////////////////////////////////////////////////////////////////// // Ready means pending, not blocked and either not scheduled or scheduled before // now. -bool Task::is_ready () const -{ - return getStatus () == Task::pending && - ! is_blocked && - (! has ("scheduled") || - Datetime ("now").operator> (get_date ("scheduled"))); +bool Task::is_ready() const { + return getStatus() == Task::pending && !is_blocked && + (!has("scheduled") || Datetime("now").operator>(get_date("scheduled"))); } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_due () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_due() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Task::dateState state = getDateState ("due"); - if (state == dateAfterToday || - state == dateEarlierToday || - state == dateLaterToday) + if (status != Task::completed && status != Task::deleted) { + Task::dateState state = getDateState("due"); + if (state == dateAfterToday || state == dateEarlierToday || state == dateLaterToday) return true; } } @@ -421,17 +376,12 @@ bool Task::is_due () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_dueyesterday () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_dueyesterday() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - if (Datetime ("yesterday").sameDay (get_date ("due"))) - return true; + if (status != Task::completed && status != Task::deleted) { + if (Datetime("yesterday").sameDay(get_date("due"))) return true; } } @@ -439,19 +389,13 @@ bool Task::is_dueyesterday () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_duetoday () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_duetoday() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Task::dateState state = getDateState ("due"); - if (state == dateEarlierToday || - state == dateLaterToday) - return true; + if (status != Task::completed && status != Task::deleted) { + Task::dateState state = getDateState("due"); + if (state == dateEarlierToday || state == dateLaterToday) return true; } } @@ -459,17 +403,12 @@ bool Task::is_duetoday () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_duetomorrow () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_duetomorrow() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - if (Datetime ("tomorrow").sameDay (get_date ("due"))) - return true; + if (status != Task::completed && status != Task::deleted) { + if (Datetime("tomorrow").sameDay(get_date("due"))) return true; } } @@ -477,19 +416,13 @@ bool Task::is_duetomorrow () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_dueweek () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_dueweek() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Datetime due (get_date ("due")); - if (due >= Datetime ("sow") && - due <= Datetime ("eow")) - return true; + if (status != Task::completed && status != Task::deleted) { + Datetime due(get_date("due")); + if (due >= Datetime("sow") && due <= Datetime("eow")) return true; } } @@ -497,19 +430,13 @@ bool Task::is_dueweek () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_duemonth () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_duemonth() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Datetime due (get_date ("due")); - if (due >= Datetime ("som") && - due <= Datetime ("eom")) - return true; + if (status != Task::completed && status != Task::deleted) { + Datetime due(get_date("due")); + if (due >= Datetime("som") && due <= Datetime("eom")) return true; } } @@ -517,19 +444,13 @@ bool Task::is_duemonth () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_duequarter () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_duequarter() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Datetime due (get_date ("due")); - if (due >= Datetime ("soq") && - due <= Datetime ("eoq")) - return true; + if (status != Task::completed && status != Task::deleted) { + Datetime due(get_date("due")); + if (due >= Datetime("soq") && due <= Datetime("eoq")) return true; } } @@ -537,19 +458,13 @@ bool Task::is_duequarter () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_dueyear () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_dueyear() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted) - { - Datetime due (get_date ("due")); - if (due >= Datetime ("soy") && - due <= Datetime ("eoy")) - return true; + if (status != Task::completed && status != Task::deleted) { + Datetime due(get_date("due")); + if (due >= Datetime("soy") && due <= Datetime("eoy")) return true; } } @@ -557,44 +472,31 @@ bool Task::is_dueyear () const } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_udaPresent () const -{ - for (auto& col : Context::getContext ().columns) - if (col.second->is_uda () && - has (col.first)) - return true; +bool Task::is_udaPresent() const { + for (auto& col : Context::getContext().columns) + if (col.second->is_uda() && has(col.first)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_orphanPresent () const -{ +bool Task::is_orphanPresent() const { for (auto& att : data) - if (! isAnnotationAttr (att.first) && - ! isTagAttr (att.first) && - ! isDepAttr (att.first) && - Context::getContext ().columns.find (att.first) == Context::getContext ().columns.end ()) + if (!isAnnotationAttr(att.first) && !isTagAttr(att.first) && !isDepAttr(att.first) && + Context::getContext().columns.find(att.first) == Context::getContext().columns.end()) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -bool Task::is_overdue () const -{ - if (has ("due")) - { - Task::status status = getStatus (); +bool Task::is_overdue() const { + if (has("due")) { + Task::status status = getStatus(); - if (status != Task::completed && - status != Task::deleted && - status != Task::recurring) - { - Task::dateState state = getDateState ("due"); - if (state == dateEarlierToday || - state == dateBeforeToday) - return true; + if (status != Task::completed && status != Task::deleted && status != Task::recurring) { + Task::dateState state = getDateState("due"); + if (state == dateEarlierToday || state == dateBeforeToday) return true; } } @@ -608,92 +510,23 @@ bool Task::is_overdue () const // While this is not consistent with other attribute-based virtual tags, such // as +BLOCKED, it is more backwards compatible with how +WAITING virtual tag // behaved in the past, when waiting had a dedicated status value. -bool Task::is_waiting () const -{ - if (has ("wait") && get ("status") == "pending") - { +bool Task::is_waiting() const { + if (has("wait") && get("status") == "pending") { Datetime now; - Datetime wait (get_date ("wait")); - if (wait > now) - return true; + Datetime wait(get_date("wait")); + if (wait > now) return true; } return false; } //////////////////////////////////////////////////////////////////////////////// -// Attempt an FF4 parse first, using Task::parse, and in the event of an error -// try a JSON parse, otherwise a legacy parse (currently no legacy formats are -// supported). -// -// Note that FF1, FF2 and FF3 are no longer supported. -// -// start --> [ --> Att --> ] --> end -// ^ | -// +-------+ -// -void Task::parse (const std::string& input) -{ - try - { - // File format version 4, from 2009-5-16 - now, v1.7.1+ - // This is the parse format tried first, because it is most used. - data.clear (); - - if (input[0] == '[') - { - // Not using Pig to parse here (which would be idiomatic), because we - // don't need to differentiate betwen utf-8 and normal characters. - // Pig's scanning the string can be expensive. - auto ending_bracket = input.find_last_of (']'); - if (ending_bracket != std::string::npos) - { - std::string line = input.substr(1, ending_bracket); - - if (line.length () == 0) - throw std::string ("Empty record in input."); - - Pig attLine (line); - std::string name; - std::string value; - while (!attLine.eos ()) - { - if (attLine.getUntilAscii (':', name) && - attLine.skip (':') && - attLine.getQuoted ('"', value)) - { -#ifdef PRODUCT_TASKWARRIOR - legacyAttributeMap (name); -#endif - - if (isAnnotationAttr (name)) - ++annotation_count; - - data[name] = decode (json::decode (value)); - } - - attLine.skip (' '); - } - - std::string remainder; - attLine.getRemainder (remainder); - if (remainder.length ()) - throw std::string ("Unrecognized characters at end of line."); - } - } - else if (input[0] == '{') - parseJSON (input); - else - throw std::string ("Record not recognized as format 4."); - } - - catch (const std::string&) - { - parseLegacy (input); - } +// Try a JSON parse. +void Task::parse(const std::string& input) { + parseJSON(input); // for compatibility, include all tags in `tags` as `tag_..` attributes - if (data.find ("tags") != data.end ()) { + if (data.find("tags") != data.end()) { for (auto& tag : split(data["tags"], ',')) { data[tag2Attr(tag)] = "x"; } @@ -702,7 +535,7 @@ void Task::parse (const std::string& input) fixTagsAttribute(); // same for `depends` / `dep_..` - if (data.find ("depends") != data.end ()) { + if (data.find("depends") != data.end()) { for (auto& dep : split(data["depends"], ',')) { data[dep2Attr(dep)] = "x"; } @@ -714,27 +547,21 @@ void Task::parse (const std::string& input) //////////////////////////////////////////////////////////////////////////////// // Note that all fields undergo encode/decode. -void Task::parseJSON (const std::string& line) -{ +void Task::parseJSON(const std::string& line) { // Parse the whole thing. - json::value* root = json::parse (line); - if (root && - root->type () == json::j_object) - parseJSON ((json::object*) root); + json::value* root = json::parse(line); + if (root && root->type() == json::j_object) parseJSON((json::object*)root); delete root; } //////////////////////////////////////////////////////////////////////////////// -void Task::parseJSON (const json::object* root_obj) -{ +void Task::parseJSON(const json::object* root_obj) { // For each object element... - for (auto& i : root_obj->_data) - { + for (auto& i : root_obj->_data) { // If the attribute is a recognized column. std::string type = Task::attributes[i.first]; - if (type != "") - { + if (type != "") { // Any specified id is ignored. if (i.first == "id") ; @@ -744,51 +571,44 @@ void Task::parseJSON (const json::object* root_obj) ; // TW-1274 Standardization. - else if (i.first == "modification") - { - auto text = i.second->dump (); - Lexer::dequote (text); - Datetime d (text); - set ("modified", d.toEpochString ()); + else if (i.first == "modification") { + auto text = i.second->dump(); + Lexer::dequote(text); + Datetime d(text); + set("modified", d.toEpochString()); } // Dates are converted from ISO to epoch. - else if (type == "date") - { - auto text = i.second->dump (); - Lexer::dequote (text); - Datetime d (text); - set (i.first, text == "" ? "" : d.toEpochString ()); + else if (type == "date") { + auto text = i.second->dump(); + Lexer::dequote(text); + Datetime d(text); + set(i.first, text == "" ? "" : d.toEpochString()); } // Tags are an array of JSON strings. - else if (i.first == "tags" && i.second->type() == json::j_array) - { + else if (i.first == "tags" && i.second->type() == json::j_array) { auto tags = (json::array*)i.second; - for (auto& t : tags->_data) - { + for (auto& t : tags->_data) { auto tag = (json::string*)t; - addTag (tag->_data); + addTag(tag->_data); } } // Dependencies can be exported as an array of strings. // 2016-02-21: This will be the only option in future releases. // See other 2016-02-21 comments for details. - else if (i.first == "depends" && i.second->type() == json::j_array) - { + else if (i.first == "depends" && i.second->type() == json::j_array) { auto deps = (json::array*)i.second; - for (auto& t : deps->_data) - { + for (auto& t : deps->_data) { auto dep = (json::string*)t; - addDependency (dep->_data); + addDependency(dep->_data); } } // Dependencies can be exported as a single comma-separated string. // 2016-02-21: Deprecated - see other 2016-02-21 comments for details. - else if (i.first == "depends" && i.second->type() == json::j_string) - { + else if (i.first == "depends" && i.second->type() == json::j_string) { auto deps = (json::string*)i.second; // Fix for issue#2689: taskserver sometimes encodes the depends @@ -797,64 +617,57 @@ void Task::parseJSON (const json::object* root_obj) // it invalid JSON. Since we know the characters we're looking for, // we'll just filter out everything else. std::string deps_str = deps->_data; - if (deps_str.front () == '[' && deps_str.back () == ']') { + if (deps_str.front() == '[' && deps_str.back() == ']') { std::string filtered; - for (auto &c: deps_str) { - if ((c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || - c == ',' || c == '-') { - filtered.push_back(c); - } - } - deps_str = filtered; + for (auto& c : deps_str) { + if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || c == ',' || c == '-') { + filtered.push_back(c); + } + } + deps_str = filtered; } - auto uuids = split (deps_str, ','); + auto uuids = split(deps_str, ','); - for (const auto& uuid : uuids) - addDependency (uuid); + for (const auto& uuid : uuids) addDependency(uuid); } // Strings are decoded. - else if (type == "string") - { - auto text = i.second->dump (); - Lexer::dequote (text); - set (i.first, json::decode (text)); + else if (type == "string") { + auto text = i.second->dump(); + Lexer::dequote(text); + set(i.first, json::decode(text)); } // Other types are simply added. - else - { - auto text = i.second->dump (); - Lexer::dequote (text); - set (i.first, text); + else { + auto text = i.second->dump(); + Lexer::dequote(text); + set(i.first, text); } } // UDA orphans and annotations do not have columns. - else - { + else { // Annotations are an array of JSON objects with 'entry' and // 'description' values and must be converted. - if (i.first == "annotations") - { - std::map annos; + if (i.first == "annotations") { + std::map annos; // Fail if 'annotations' is not an array if (i.second->type() != json::j_array) { - throw format ("Annotations is malformed: {1}", i.second->dump ()); + throw format("Annotations is malformed: {1}", i.second->dump()); } auto atts = (json::array*)i.second; - for (auto& annotations : atts->_data) - { + for (auto& annotations : atts->_data) { auto annotation = (json::object*)annotations; // Extract description. Fail if not present. auto what = (json::string*)annotation->_data["description"]; - if (! what) { - annotation->_data.erase ("description"); // Erase NULL description inserted by failed lookup above - throw format ("Annotation is missing a description: {1}", annotation->dump ()); + if (!what) { + annotation->_data.erase( + "description"); // Erase NULL description inserted by failed lookup above + throw format("Annotation is missing a description: {1}", annotation->dump()); } // Extract 64-bit annotation entry value @@ -864,10 +677,10 @@ void Task::parseJSON (const json::object* root_obj) // Extract entry. Use current time if not present. auto when = (json::string*)annotation->_data["entry"]; if (when) - ann_timestamp = (long long) (Datetime (when->_data).toEpoch ()); + ann_timestamp = (long long)(Datetime(when->_data).toEpoch()); else { - annotation->_data.erase ("entry"); // Erase NULL entry inserted by failed lookup above - ann_timestamp = (long long) (Datetime ().toEpoch ()); + annotation->_data.erase("entry"); // Erase NULL entry inserted by failed lookup above + ann_timestamp = (long long)(Datetime().toEpoch()); } std::stringstream name; @@ -875,34 +688,29 @@ void Task::parseJSON (const json::object* root_obj) // Increment the entry timestamp in case of a conflict. Same // behaviour as CmdAnnotate. - while (annos.find(name.str ()) != annos.end ()) - { - name.str (""); // Clear - ann_timestamp++; - name << "annotation_" << ann_timestamp; + while (annos.find(name.str()) != annos.end()) { + name.str(""); // Clear + ann_timestamp++; + name << "annotation_" << ann_timestamp; } - annos.emplace (name.str (), json::decode (what->_data)); + annos.emplace(name.str(), json::decode(what->_data)); } - setAnnotations (annos); + setAnnotations(annos); } // UDA Orphan - must be preserved. - else - { + else { #ifdef PRODUCT_TASKWARRIOR std::stringstream message; - message << "Task::parseJSON found orphan '" - << i.first - << "' with value '" - << i.second + message << "Task::parseJSON found orphan '" << i.first << "' with value '" << i.second << "' --> preserved\n"; - Context::getContext ().debug (message.str ()); + Context::getContext().debug(message.str()); #endif - auto text = i.second->dump (); - Lexer::dequote (text); - set (i.first, json::decode (text)); + auto text = i.second->dump(); + Lexer::dequote(text); + set(i.first, json::decode(text)); } } } @@ -910,171 +718,124 @@ void Task::parseJSON (const json::object* root_obj) //////////////////////////////////////////////////////////////////////////////// // Note that all fields undergo encode/decode. -void Task::parseTC (const tc::Task& task) -{ - data = task.get_taskmap (); +void Task::parseTC(rust::Box task) { + auto items = task->items(); + data.clear(); + for (auto& item : items) { + data[static_cast(item.prop)] = static_cast(item.value); + } // count annotations annotation_count = 0; - for (auto i : data) - { - if (isAnnotationAttr (i.first)) - { + for (auto i : data) { + if (isAnnotationAttr(i.first)) { ++annotation_count; } } - data["uuid"] = task.get_uuid (); - id = Context::getContext ().tdb2.id (data["uuid"]); - - is_blocking = task.is_blocking(); - is_blocked = task.is_blocked(); + data["uuid"] = static_cast(task->get_uuid().to_string()); + id = Context::getContext().tdb2.id(data["uuid"]); } //////////////////////////////////////////////////////////////////////////////// // No legacy formats are currently supported as of 2.4.0. -void Task::parseLegacy (const std::string& line) -{ - switch (determineVersion (line)) - { - // File format version 1, from 2006-11-27 - 2007-12-31, v0.x+ - v0.9.3 - case 1: throw std::string ("Taskwarrior no longer supports file format 1, originally used between 27 November 2006 and 31 December 2007."); +void Task::parseLegacy(const std::string& line) { + switch (determineVersion(line)) { + // File format version 1, from 2006-11-27 - 2007-12-31, v0.x+ - v0.9.3 + case 1: + throw std::string( + "Taskwarrior no longer supports file format 1, originally used between 27 November 2006 " + "and 31 December 2007."); - // File format version 2, from 2008-1-1 - 2009-3-23, v0.9.3 - v1.5.0 - case 2: throw std::string ("Taskwarrior no longer supports file format 2, originally used between 1 January 2008 and 12 April 2009."); + // File format version 2, from 2008-1-1 - 2009-3-23, v0.9.3 - v1.5.0 + case 2: + throw std::string( + "Taskwarrior no longer supports file format 2, originally used between 1 January 2008 " + "and 12 April 2009."); - // File format version 3, from 2009-3-23 - 2009-05-16, v1.6.0 - v1.7.1 - case 3: throw std::string ("Taskwarrior no longer supports file format 3, originally used between 23 March 2009 and 16 May 2009."); + // File format version 3, from 2009-3-23 - 2009-05-16, v1.6.0 - v1.7.1 + case 3: + throw std::string( + "Taskwarrior no longer supports file format 3, originally used between 23 March 2009 and " + "16 May 2009."); - // File format version 4, from 2009-05-16 - today, v1.7.1+ - case 4: - break; + // File format version 4, from 2009-05-16 - today, v1.7.1+ + case 4: + break; - default: + default: #ifdef PRODUCT_TASKWARRIOR - std::stringstream message; - message << "Invalid fileformat at line '" - << line - << '\''; - Context::getContext ().debug (message.str ()); + std::stringstream message; + message << "Invalid fileformat at line '" << line << '\''; + Context::getContext().debug(message.str()); #endif - throw std::string ("Unrecognized Taskwarrior file format or blank line in data."); - break; + throw std::string("Unrecognized Taskwarrior file format or blank line in data."); + break; } recalc_urgency = true; } //////////////////////////////////////////////////////////////////////////////// -// The format is: -// -// [ : ... ] -// -std::string Task::composeF4 () const -{ - std::string ff4 = "["; - - bool first = true; - for (const auto& it : data) - { - // Orphans have no type, treat as string. - std::string type = Task::attributes[it.first]; - if (type == "") - type = "string"; - - // If there is a value. - if (it.second != "") - { - ff4 += (first ? "" : " "); - ff4 += it.first; - ff4 += ":\""; - if (type == "string") - ff4 += encode (json::encode (it.second)); - else - ff4 += it.second; - ff4 += '"'; - - first = false; - } - } - - ff4 += ']'; - return ff4; -} - -//////////////////////////////////////////////////////////////////////////////// -std::string Task::composeJSON (bool decorate /*= false*/) const -{ +std::string Task::composeJSON(bool decorate /*= false*/) { std::stringstream out; out << '{'; // ID inclusion is optional, but not a good idea, because it remains correct // only until the next gc. - if (decorate) - out << "\"id\":" << id << ','; + if (decorate) out << "\"id\":" << id << ','; // First the non-annotations. int attributes_written = 0; - for (auto& i : data) - { + for (auto& i : data) { // Annotations are not written out here. - if (! i.first.compare (0, 11, "annotation_", 11)) - continue; + if (!i.first.compare(0, 11, "annotation_", 11)) continue; // Tags and dependencies are handled below - if (i.first == "tags" || isTagAttr (i.first)) - continue; - if (i.first == "depends" || isDepAttr (i.first)) - continue; + if (i.first == "tags" || isTagAttr(i.first)) continue; + if (i.first == "depends" || isDepAttr(i.first)) continue; // If value is an empty string, do not ever output it - if (i.second == "") - continue; - - if (attributes_written) - out << ','; + if (i.second == "") continue; std::string type = Task::attributes[i.first]; - if (type == "") - type = "string"; + if (type == "") type = "string"; // Date fields are written as ISO 8601. - if (type == "date") - { - Datetime d (i.second); - out << '"' - << (i.first == "modification" ? "modified" : i.first) - << "\":\"" - // Date was deleted, do not export parsed empty string - << (i.second == "" ? "" : d.toISO ()) - << '"'; + if (type == "date") { + time_t epoch = get_date(i.first); + if (epoch != 0) { + Datetime d(i.second); + if (attributes_written) out << ','; - ++attributes_written; + out << '"' << (i.first == "modification" ? "modified" : i.first) + << "\":\"" + // Date was deleted, do not export parsed empty string + << (i.second == "" ? "" : d.toISO()) << '"'; + + ++attributes_written; + } } -/* - else if (type == "duration") - { - // TODO Emit Datetime - } -*/ - else if (type == "numeric") - { - out << '"' - << i.first - << "\":" - << i.second; + /* + else if (type == "duration") + { + // TODO Emit Datetime + } + */ + else if (type == "numeric") { + if (attributes_written) out << ','; + + out << '"' << i.first << "\":" << i.second; ++attributes_written; } // Everything else is a quoted value. - else - { - out << '"' - << i.first - << "\":\"" - << (type == "string" ? json::encode (i.second) : i.second) + else { + if (attributes_written) out << ','; + + out << '"' << i.first << "\":\"" << (type == "string" ? json::encode(i.second) : i.second) << '"'; ++attributes_written; @@ -1082,24 +843,16 @@ std::string Task::composeJSON (bool decorate /*= false*/) const } // Now the annotations, if any. - if (annotation_count) - { - out << ',' - << "\"annotations\":["; + if (annotation_count) { + out << ',' << "\"annotations\":["; int annotations_written = 0; - for (auto& i : data) - { - if (! i.first.compare (0, 11, "annotation_", 11)) - { - if (annotations_written) - out << ','; + for (auto& i : data) { + if (!i.first.compare(0, 11, "annotation_", 11)) { + if (annotations_written) out << ','; - Datetime d (i.first.substr (11)); - out << R"({"entry":")" - << d.toISO () - << R"(","description":")" - << json::encode (i.second) + Datetime d(i.first.substr(11)); + out << R"({"entry":")" << d.toISO() << R"(","description":")" << json::encode(i.second) << "\"}"; ++annotations_written; @@ -1110,16 +863,12 @@ std::string Task::composeJSON (bool decorate /*= false*/) const } auto tags = getTags(); - if (tags.size() > 0) - { - out << ',' - << "\"tags\":["; + if (tags.size() > 0) { + out << ',' << "\"tags\":["; int count = 0; - for (const auto& tag : tags) - { - if (count++) - out << ','; + for (const auto& tag : tags) { + if (count++) out << ','; out << '"' << tag << '"'; } @@ -1128,17 +877,13 @@ std::string Task::composeJSON (bool decorate /*= false*/) const ++attributes_written; } - auto depends = getDependencyUUIDs (); - if (depends.size() > 0) - { - out << ',' - << "\"depends\":["; + auto depends = getDependencyUUIDs(); + if (depends.size() > 0) { + out << ',' << "\"depends\":["; int count = 0; - for (const auto& dep : depends) - { - if (count++) - out << ','; + for (const auto& dep : depends) { + if (count++) out << ','; out << '"' << dep << '"'; } @@ -1149,32 +894,24 @@ std::string Task::composeJSON (bool decorate /*= false*/) const #ifdef PRODUCT_TASKWARRIOR // Include urgency. - if (decorate) - out << ',' - << "\"urgency\":" - << urgency_c (); + if (decorate) out << ',' << "\"urgency\":" << urgency(); #endif out << '}'; - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// -int Task::getAnnotationCount () const -{ +int Task::getAnnotationCount() const { int count = 0; for (auto& ann : data) - if (! ann.first.compare (0, 11, "annotation_", 11)) - ++count; + if (!ann.first.compare(0, 11, "annotation_", 11)) ++count; return count; } //////////////////////////////////////////////////////////////////////////////// -bool Task::hasAnnotations () const -{ - return annotation_count ? true : false; -} +bool Task::hasAnnotations() const { return annotation_count ? true : false; } //////////////////////////////////////////////////////////////////////////////// // The timestamp is part of the name: @@ -1182,36 +919,29 @@ bool Task::hasAnnotations () const // // Note that the time is incremented (one second) in order to find a unique // timestamp. -void Task::addAnnotation (const std::string& description) -{ - time_t now = time (nullptr); +void Task::addAnnotation(const std::string& description) { + time_t now = time(nullptr); std::string key; - do - { - key = "annotation_" + format ((long long int) now); + do { + key = "annotation_" + format((long long int)now); ++now; - } - while (has (key)); + } while (has(key)); - data[key] = json::decode (description); + data[key] = description; ++annotation_count; recalc_urgency = true; } //////////////////////////////////////////////////////////////////////////////// -void Task::removeAnnotations () -{ +void Task::removeAnnotations() { // Erase old annotations. - auto i = data.begin (); - while (i != data.end ()) - { - if (! i->first.compare (0, 11, "annotation_", 11)) - { + auto i = data.begin(); + while (i != data.end()) { + if (!i->first.compare(0, 11, "annotation_", 11)) { --annotation_count; - data.erase (i++); - } - else + data.erase(i++); + } else i++; } @@ -1219,44 +949,37 @@ void Task::removeAnnotations () } //////////////////////////////////////////////////////////////////////////////// -std::map Task::getAnnotations () const -{ - std::map a; +std::map Task::getAnnotations() const { + std::map a; for (auto& ann : data) - if (! ann.first.compare (0, 11, "annotation_", 11)) - a.insert (ann); + if (!ann.first.compare(0, 11, "annotation_", 11)) a.insert(ann); return a; } //////////////////////////////////////////////////////////////////////////////// -void Task::setAnnotations (const std::map & annotations) -{ +void Task::setAnnotations(const std::map& annotations) { // Erase old annotations. - removeAnnotations (); + removeAnnotations(); - for (auto& anno : annotations) - data.insert (anno); + for (auto& anno : annotations) data.insert(anno); - annotation_count = annotations.size (); + annotation_count = annotations.size(); recalc_urgency = true; } #ifdef PRODUCT_TASKWARRIOR //////////////////////////////////////////////////////////////////////////////// -void Task::addDependency (int depid) -{ +void Task::addDependency(int depid) { // Check that id is resolvable. - std::string uuid = Context::getContext ().tdb2.uuid (depid); - if (uuid == "") - throw format ("Could not create a dependency on task {1} - not found.", depid); + std::string uuid = Context::getContext().tdb2.uuid(depid); + if (uuid == "") throw format("Could not create a dependency on task {1} - not found.", depid); // the addDependency(&std::string) overload will check this, too, but here we // can give an more natural error message containing the id the user // provided. - if (hasDependency (uuid)) - { - Context::getContext ().footnote (format ("Task {1} already depends on task {2}.", id, depid)); + if (hasDependency(uuid)) { + Context::getContext().footnote(format("Task {1} already depends on task {2}.", id, depid)); return; } @@ -1265,26 +988,24 @@ void Task::addDependency (int depid) #endif //////////////////////////////////////////////////////////////////////////////// -void Task::addDependency (const std::string& uuid) -{ - if (uuid == get ("uuid")) - throw std::string ("A task cannot be dependent on itself."); +void Task::addDependency(const std::string& uuid) { + if (uuid == get("uuid")) throw std::string("A task cannot be dependent on itself."); - if (hasDependency (uuid)) - { + if (hasDependency(uuid)) { #ifdef PRODUCT_TASKWARRIOR - Context::getContext ().footnote (format ("Task {1} already depends on task {2}.", get ("uuid"), uuid)); + Context::getContext().footnote( + format("Task {1} already depends on task {2}.", get("uuid"), uuid)); #endif return; } // Store the dependency. - set (dep2Attr (uuid), "x"); + set(dep2Attr(uuid), "x"); // Prevent circular dependencies. #ifdef PRODUCT_TASKWARRIOR - if (dependencyIsCircular (*this)) - throw std::string ("Circular dependency detected and disallowed."); + if (dependencyIsCircular(*this)) + throw std::string("Circular dependency detected and disallowed."); #endif recalc_urgency = true; @@ -1293,106 +1014,94 @@ void Task::addDependency (const std::string& uuid) #ifdef PRODUCT_TASKWARRIOR //////////////////////////////////////////////////////////////////////////////// -void Task::removeDependency (int id) -{ - std::string uuid = Context::getContext ().tdb2.uuid (id); +void Task::removeDependency(int id) { + std::string uuid = Context::getContext().tdb2.uuid(id); // The removeDependency(std::string&) method will check this too, but here we // can give a more natural error message containing the id provided by the user - if (uuid == "" || !has (dep2Attr (uuid))) - throw format ("Could not delete a dependency on task {1} - not found.", id); - removeDependency (uuid); + if (uuid == "" || !has(dep2Attr(uuid))) + throw format("Could not delete a dependency on task {1} - not found.", id); + removeDependency(uuid); } //////////////////////////////////////////////////////////////////////////////// -void Task::removeDependency (const std::string& uuid) -{ - auto depattr = dep2Attr (uuid); - if (has (depattr)) - remove (depattr); +void Task::removeDependency(const std::string& uuid) { + auto depattr = dep2Attr(uuid); + if (has(depattr)) + remove(depattr); else - throw format ("Could not delete a dependency on task {1} - not found.", uuid); + throw format("Could not delete a dependency on task {1} - not found.", uuid); recalc_urgency = true; fixDependsAttribute(); } //////////////////////////////////////////////////////////////////////////////// -bool Task::hasDependency (const std::string& uuid) const -{ - auto depattr = dep2Attr (uuid); - return has (depattr); +bool Task::hasDependency(const std::string& uuid) const { + auto depattr = dep2Attr(uuid); + return has(depattr); } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::getDependencyIDs () const -{ - std::vector ids; - for (auto& attr : all ()) { - if (!isDepAttr (attr)) - continue; - auto dep = attr2Dep (attr); - ids.push_back (Context::getContext ().tdb2.id (dep)); +std::vector Task::getDependencyIDs() const { + std::vector ids; + for (auto& attr : all()) { + if (!isDepAttr(attr)) continue; + auto dep = attr2Dep(attr); + ids.push_back(Context::getContext().tdb2.id(dep)); } return ids; } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::getDependencyUUIDs () const -{ - std::vector uuids; - for (auto& attr : all ()) { - if (!isDepAttr (attr)) - continue; - auto dep = attr2Dep (attr); - uuids.push_back (dep); +std::vector Task::getDependencyUUIDs() const { + std::vector uuids; + for (auto& attr : all()) { + if (!isDepAttr(attr)) continue; + auto dep = attr2Dep(attr); + uuids.push_back(dep); } return uuids; } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::getDependencyTasks () const -{ - auto uuids = getDependencyUUIDs (); +std::vector Task::getDependencyTasks() const { + auto uuids = getDependencyUUIDs(); // NOTE: this may seem inefficient, but note that `TDB2::get` performs a // linear search on each invocation, so scanning *once* is quite a bit more // efficient. - std::vector blocking; + std::vector blocking; if (uuids.size() > 0) - for (auto& it : Context::getContext ().tdb2.pending_tasks ()) - if (it.getStatus () != Task::completed && - it.getStatus () != Task::deleted && - std::find (uuids.begin (), uuids.end (), it.get ("uuid")) != uuids.end ()) - blocking.push_back (it); + for (auto& it : Context::getContext().tdb2.pending_tasks()) + if (it.getStatus() != Task::completed && it.getStatus() != Task::deleted && + std::find(uuids.begin(), uuids.end(), it.get("uuid")) != uuids.end()) + blocking.push_back(it); return blocking; } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::getBlockedTasks () const -{ - auto uuid = get ("uuid"); +std::vector Task::getBlockedTasks() const { + auto uuid = get("uuid"); - std::vector blocked; - for (auto& it : Context::getContext ().tdb2.pending_tasks ()) - if (it.getStatus () != Task::completed && - it.getStatus () != Task::deleted && - it.hasDependency (uuid)) - blocked.push_back (it); + std::vector blocked; + for (auto& it : Context::getContext().tdb2.pending_tasks()) + if (it.getStatus() != Task::completed && it.getStatus() != Task::deleted && + it.hasDependency(uuid)) + blocked.push_back(it); return blocked; } #endif //////////////////////////////////////////////////////////////////////////////// -int Task::getTagCount () const -{ +int Task::getTagCount() const { auto count = 0; for (auto& attr : data) { - if (isTagAttr (attr.first)) { + if (isTagAttr(attr.first)) { count++; } } @@ -1410,229 +1119,203 @@ int Task::getTagCount () const // due:1month - - - - - - - ? // due:1year - - - - - - - - // -bool Task::hasTag (const std::string& tag) const -{ +bool Task::hasTag(const std::string& tag) const { // Synthetic tags - dynamically generated, but do not occupy storage space. // Note: This list must match that in CmdInfo::execute. // Note: This list must match that in ::feedback_reserved_tags. - if (isupper (tag[0])) - { + if (isupper(tag[0])) { // NOTE: This list should be kept synchronized with: // * the list in CmdTags.cpp for the _tags command. // * the list in CmdInfo.cpp for the info command. - if (tag == "BLOCKED") return is_blocked; + if (tag == "BLOCKED") return is_blocked; if (tag == "UNBLOCKED") return !is_blocked; - if (tag == "BLOCKING") return is_blocking; + if (tag == "BLOCKING") return is_blocking; #ifdef PRODUCT_TASKWARRIOR - if (tag == "READY") return is_ready (); - if (tag == "DUE") return is_due (); - if (tag == "DUETODAY") return is_duetoday (); // 2016-03-29: Deprecated in 2.6.0 - if (tag == "TODAY") return is_duetoday (); - if (tag == "YESTERDAY") return is_dueyesterday (); - if (tag == "TOMORROW") return is_duetomorrow (); - if (tag == "OVERDUE") return is_overdue (); - if (tag == "WEEK") return is_dueweek (); - if (tag == "MONTH") return is_duemonth (); - if (tag == "QUARTER") return is_duequarter (); - if (tag == "YEAR") return is_dueyear (); + if (tag == "READY") return is_ready(); + if (tag == "DUE") return is_due(); + if (tag == "DUETODAY") return is_duetoday(); // 2016-03-29: Deprecated in 2.6.0 + if (tag == "TODAY") return is_duetoday(); + if (tag == "YESTERDAY") return is_dueyesterday(); + if (tag == "TOMORROW") return is_duetomorrow(); + if (tag == "OVERDUE") return is_overdue(); + if (tag == "WEEK") return is_dueweek(); + if (tag == "MONTH") return is_duemonth(); + if (tag == "QUARTER") return is_duequarter(); + if (tag == "YEAR") return is_dueyear(); #endif - if (tag == "ACTIVE") return has ("start"); - if (tag == "SCHEDULED") return has ("scheduled"); - if (tag == "CHILD") return has ("parent") || has ("template"); // 2017-01-07: Deprecated in 2.6.0 - if (tag == "INSTANCE") return has ("template") || has ("parent"); - if (tag == "UNTIL") return has ("until"); - if (tag == "ANNOTATED") return hasAnnotations (); - if (tag == "TAGGED") return getTagCount() > 0; - if (tag == "PARENT") return has ("mask") || has ("last"); // 2017-01-07: Deprecated in 2.6.0 - if (tag == "TEMPLATE") return has ("last") || has ("mask"); - if (tag == "WAITING") return is_waiting (); - if (tag == "PENDING") return getStatus () == Task::pending; - if (tag == "COMPLETED") return getStatus () == Task::completed; - if (tag == "DELETED") return getStatus () == Task::deleted; + if (tag == "ACTIVE") return has("start"); + if (tag == "SCHEDULED") return has("scheduled"); + if (tag == "CHILD") return has("parent") || has("template"); // 2017-01-07: Deprecated in 2.6.0 + if (tag == "INSTANCE") return has("template") || has("parent"); + if (tag == "UNTIL") return has("until"); + if (tag == "ANNOTATED") return hasAnnotations(); + if (tag == "TAGGED") return getTagCount() > 0; + if (tag == "PARENT") return has("mask") || has("last"); // 2017-01-07: Deprecated in 2.6.0 + if (tag == "TEMPLATE") return has("last") || has("mask"); + if (tag == "WAITING") return is_waiting(); + if (tag == "PENDING") return getStatus() == Task::pending; + if (tag == "COMPLETED") return getStatus() == Task::completed; + if (tag == "DELETED") return getStatus() == Task::deleted; #ifdef PRODUCT_TASKWARRIOR - if (tag == "UDA") return is_udaPresent (); - if (tag == "ORPHAN") return is_orphanPresent (); - if (tag == "LATEST") return id == Context::getContext ().tdb2.latest_id (); + if (tag == "UDA") return is_udaPresent(); + if (tag == "ORPHAN") return is_orphanPresent(); + if (tag == "LATEST") return id == Context::getContext().tdb2.latest_id(); #endif - if (tag == "PROJECT") return has ("project"); - if (tag == "PRIORITY") return has ("priority"); + if (tag == "PROJECT") return has("project"); + if (tag == "PRIORITY") return has("priority"); } // Concrete tags. - if (has (tag2Attr (tag))) - return true; + if (has(tag2Attr(tag))) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -void Task::addTag (const std::string& tag) -{ - auto attr = tag2Attr (tag); - if (!has (attr)) { - set (attr, "x"); +void Task::addTag(const std::string& tag) { + auto attr = tag2Attr(tag); + if (!has(attr)) { + set(attr, "x"); recalc_urgency = true; fixTagsAttribute(); } } //////////////////////////////////////////////////////////////////////////////// -void Task::setTags (const std::vector & tags) -{ +void Task::setTags(const std::vector& tags) { auto existing = getTags(); // edit in-place, determining which should be // added and which should be removed - std::vector toAdd; - std::vector toRemove; + std::vector toAdd; + std::vector toRemove; for (auto& tag : tags) { - if (std::find (existing.begin (), existing.end (), tag) == existing.end ()) - toAdd.push_back(tag); + if (std::find(existing.begin(), existing.end(), tag) == existing.end()) toAdd.push_back(tag); } - for (auto& tag : getTags ()) { - if (std::find (tags.begin (), tags.end (), tag) == tags.end ()) { - toRemove.push_back (tag); + for (auto& tag : getTags()) { + if (std::find(tags.begin(), tags.end(), tag) == tags.end()) { + toRemove.push_back(tag); } } for (auto& tag : toRemove) { - removeTag (tag); + removeTag(tag); } for (auto& tag : toAdd) { - addTag (tag); + addTag(tag); } // (note: addTag / removeTag took care of recalculating urgency) } //////////////////////////////////////////////////////////////////////////////// -std::vector Task::getTags () const -{ - std::vector tags; +std::vector Task::getTags() const { + std::vector tags; for (auto& attr : data) { - if (!isTagAttr (attr.first)) { + if (!isTagAttr(attr.first)) { continue; } - auto tag = attr2Tag (attr.first); - tags.push_back (tag); + auto tag = attr2Tag(attr.first); + tags.push_back(tag); } return tags; } //////////////////////////////////////////////////////////////////////////////// -void Task::removeTag (const std::string& tag) -{ - auto attr = tag2Attr (tag); - if (has (attr)) { - data.erase (attr); +void Task::removeTag(const std::string& tag) { + auto attr = tag2Attr(tag); + if (has(attr)) { + data.erase(attr); recalc_urgency = true; fixTagsAttribute(); } } //////////////////////////////////////////////////////////////////////////////// -void Task::fixTagsAttribute () -{ +void Task::fixTagsAttribute() { // Fix up the old `tags` attribute to match the `tag_..` attributes (or // remove it if there are no tags) - auto tags = getTags (); - if (tags.size () > 0) { - set ("tags", join (",", tags)); + auto tags = getTags(); + if (tags.size() > 0) { + set("tags", join(",", tags)); } else { - remove ("tags"); + remove("tags"); } } //////////////////////////////////////////////////////////////////////////////// -bool Task::isTagAttr(const std::string& attr) -{ - return attr.compare(0, 4, "tag_") == 0; -} +bool Task::isTagAttr(const std::string& attr) { return attr.compare(0, 4, "tag_") == 0; } //////////////////////////////////////////////////////////////////////////////// -const std::string Task::tag2Attr (const std::string& tag) const -{ +std::string Task::tag2Attr(const std::string& tag) { std::stringstream tag_attr; tag_attr << "tag_" << tag; return tag_attr.str(); } //////////////////////////////////////////////////////////////////////////////// -const std::string Task::attr2Tag (const std::string& attr) const -{ - assert (isTagAttr (attr)); +std::string Task::attr2Tag(const std::string& attr) { + assert(isTagAttr(attr)); return attr.substr(4); } //////////////////////////////////////////////////////////////////////////////// -void Task::fixDependsAttribute () -{ +void Task::fixDependsAttribute() { // Fix up the old `depends` attribute to match the `dep_..` attributes (or // remove it if there are no deps) - auto deps = getDependencyUUIDs (); - if (deps.size () > 0) { - set ("depends", join (",", deps)); + auto deps = getDependencyUUIDs(); + if (deps.size() > 0) { + set("depends", join(",", deps)); } else { - remove ("depends"); + remove("depends"); } } //////////////////////////////////////////////////////////////////////////////// -bool Task::isDepAttr(const std::string& attr) -{ - return attr.compare(0, 4, "dep_") == 0; -} +bool Task::isDepAttr(const std::string& attr) { return attr.compare(0, 4, "dep_") == 0; } //////////////////////////////////////////////////////////////////////////////// -const std::string Task::dep2Attr (const std::string& tag) const -{ +std::string Task::dep2Attr(const std::string& tag) { std::stringstream tag_attr; tag_attr << "dep_" << tag; return tag_attr.str(); } //////////////////////////////////////////////////////////////////////////////// -const std::string Task::attr2Dep (const std::string& attr) const -{ - assert (isDepAttr (attr)); +std::string Task::attr2Dep(const std::string& attr) { + assert(isDepAttr(attr)); return attr.substr(4); } //////////////////////////////////////////////////////////////////////////////// -bool Task::isAnnotationAttr(const std::string& attr) -{ +bool Task::isAnnotationAttr(const std::string& attr) { return attr.compare(0, 11, "annotation_") == 0; } #ifdef PRODUCT_TASKWARRIOR //////////////////////////////////////////////////////////////////////////////// // A UDA Orphan is an attribute that is not represented in context.columns. -std::vector Task::getUDAOrphans () const -{ - std::vector orphans; +std::vector Task::getUDAOrphans() const { + std::vector orphans; for (auto& it : data) - if (Context::getContext ().columns.find (it.first) == Context::getContext ().columns.end ()) - if (not (isAnnotationAttr (it.first) || isTagAttr (it.first) || isDepAttr (it.first))) - orphans.push_back (it.first); + if (Context::getContext().columns.find(it.first) == Context::getContext().columns.end()) + if (not(isAnnotationAttr(it.first) || isTagAttr(it.first) || isDepAttr(it.first))) + orphans.push_back(it.first); return orphans; } //////////////////////////////////////////////////////////////////////////////// -void Task::substitute ( - const std::string& from, - const std::string& to, - const std::string& flags) -{ - bool global = (flags.find ('g') != std::string::npos ? true : false); +void Task::substitute(const std::string& from, const std::string& to, const std::string& flags) { + bool global = (flags.find('g') != std::string::npos ? true : false); // Get the data to modify. - std::string description = get ("description"); - auto annotations = getAnnotations (); + std::string description = get("description"); + auto annotations = getAnnotations(); // Count the changes, so we know whether to proceed to annotations, after // modifying description. @@ -1640,260 +1323,236 @@ void Task::substitute ( bool done = false; // Regex support is optional. - if (Task::regex) - { + if (Task::regex) { // Create the regex. - RX rx (from, Task::searchCaseSensitive); - std::vector start; - std::vector end; + RX rx(from, Task::searchCaseSensitive); + std::vector start; + std::vector end; // Perform all subs on description. - if (rx.match (start, end, description)) - { + if (rx.match(start, end, description)) { int skew = 0; - for (unsigned int i = 0; i < start.size () && !done; ++i) - { - description.replace (start[i] + skew, end[i] - start[i], to); - skew += to.length () - (end[i] - start[i]); + for (unsigned int i = 0; i < start.size() && !done; ++i) { + description.replace(start[i] + skew, end[i] - start[i], to); + skew += to.length() - (end[i] - start[i]); ++changes; - if (!global) - done = true; + if (!global) done = true; } } - if (!done) - { + if (!done) { // Perform all subs on annotations. - for (auto& it : annotations) - { - start.clear (); - end.clear (); - if (rx.match (start, end, it.second)) - { + for (auto& it : annotations) { + start.clear(); + end.clear(); + if (rx.match(start, end, it.second)) { int skew = 0; - for (unsigned int i = 0; i < start.size () && !done; ++i) - { - it.second.replace (start[i + skew], end[i] - start[i], to); - skew += to.length () - (end[i] - start[i]); + for (unsigned int i = 0; i < start.size() && !done; ++i) { + it.second.replace(start[i + skew], end[i] - start[i], to); + skew += to.length() - (end[i] - start[i]); ++changes; - if (!global) - done = true; + if (!global) done = true; } } } } - } - else - { + } else { // Perform all subs on description. int counter = 0; std::string::size_type pos = 0; int skew = 0; - while ((pos = ::find (description, from, pos, Task::searchCaseSensitive)) != std::string::npos && !done) - { - description.replace (pos + skew, from.length (), to); - skew += to.length () - from.length (); + while ((pos = ::find(description, from, pos, Task::searchCaseSensitive)) != std::string::npos && + !done) { + description.replace(pos + skew, from.length(), to); + skew += to.length() - from.length(); - pos += to.length (); + pos += to.length(); ++changes; - if (!global) - done = true; + if (!global) done = true; if (++counter > APPROACHING_INFINITY) - throw format ("Terminated substitution because more than {1} changes were made - infinite loop protection.", APPROACHING_INFINITY); + throw format( + "Terminated substitution because more than {1} changes were made - infinite loop " + "protection.", + APPROACHING_INFINITY); } - if (!done) - { + if (!done) { // Perform all subs on annotations. counter = 0; - for (auto& anno : annotations) - { + for (auto& anno : annotations) { pos = 0; skew = 0; - while ((pos = ::find (anno.second, from, pos, Task::searchCaseSensitive)) != std::string::npos && !done) - { - anno.second.replace (pos + skew, from.length (), to); - skew += to.length () - from.length (); + while ((pos = ::find(anno.second, from, pos, Task::searchCaseSensitive)) != + std::string::npos && + !done) { + anno.second.replace(pos + skew, from.length(), to); + skew += to.length() - from.length(); - pos += to.length (); + pos += to.length(); ++changes; - if (!global) - done = true; + if (!global) done = true; if (++counter > APPROACHING_INFINITY) - throw format ("Terminated substitution because more than {1} changes were made - infinite loop protection.", APPROACHING_INFINITY); + throw format( + "Terminated substitution because more than {1} changes were made - infinite loop " + "protection.", + APPROACHING_INFINITY); } } } } - if (changes) - { - set ("description", description); - setAnnotations (annotations); + if (changes) { + set("description", description); + setAnnotations(annotations); recalc_urgency = true; } } #endif +//////////////////////////////////////////////////////////////////////////////// +// Validate a task for addition, raising user-visible errors for inconsistent or +// incorrect inputs. This is called before `Task::validate`. +void Task::validate_add() { + // There is no fixing a missing description. + if (!has("description")) + throw std::string("A task must have a description."); + else if (get("description") == "") + throw std::string("Cannot add a task that is blank."); + + // Cannot have an old-style recur frequency with no due date - when would it recur? + if (has("recur") && (!has("due") || get("due") == "")) + throw std::string("A recurring task must also have a 'due' date."); +} + //////////////////////////////////////////////////////////////////////////////// // The purpose of Task::validate is three-fold: // 1) To provide missing attributes where possible // 2) To provide suitable warnings about odd states -// 3) To generate errors when the inconsistencies are not fixable -// 4) To update status depending on other attributes +// 3) To update status depending on other attributes // +// As required by TaskChampion, no combination of properties and values is an +// error. This function will try to make sensible defaults and resolve inconsistencies. // Critically, note that despite the name this is not a read-only function. // -void Task::validate (bool applyDefault /* = true */) -{ +void Task::validate(bool applyDefault /* = true */) { Task::status status = Task::pending; - if (get ("status") != "") - status = getStatus (); + if (get("status") != "") status = getStatus(); // 1) Provide missing attributes where possible // Provide a UUID if necessary. Validate if present. - std::string uid = get ("uuid"); - if (has ("uuid") && uid != "") - { - Lexer lex (uid); + std::string uid = get("uuid"); + if (has("uuid") && uid != "") { + Lexer lex(uid); std::string token; Lexer::Type type; - if (! lex.isUUID (token, type, true)) - throw format ("Not a valid UUID '{1}'.", uid); - } - else - set ("uuid", uuid ()); + // `uuid` is not a property in the TaskChampion model, so an invalid UUID is + // actually an error. + if (!lex.isUUID(token, type, true)) throw format("Not a valid UUID '{1}'.", uid); + } else + set("uuid", uuid()); // TODO Obsolete remove for 3.0.0 // Recurring tasks get a special status. - if (status == Task::pending && - has ("due") && - has ("recur") && - (! has ("parent") || get ("parent") == "") && - (! has ("template") || get ("template") == "")) - { + if (status == Task::pending && has("due") && has("recur") && + (!has("parent") || get("parent") == "") && (!has("template") || get("template") == "")) { status = Task::recurring; } -/* - // TODO Add for 3.0.0 - if (status == Task::pending && - has ("due") && - has ("recur") && - (! has ("template") || get ("template") == "")) - { - status = Task::recurring; - } -*/ + /* + // TODO Add for 3.0.0 + if (status == Task::pending && + has ("due") && + has ("recur") && + (! has ("template") || get ("template") == "")) + { + status = Task::recurring; + } + */ // Tasks with a wait: date get a special status. - else if (status == Task::pending && - has ("wait") && - get ("wait") != "") + else if (status == Task::pending && has("wait") && get("wait") != "") status = Task::waiting; // By default, tasks are pending. - else if (! has ("status") || get ("status") == "") + else if (!has("status") || get("status") == "") status = Task::pending; // Default to 'periodic' type recurrence. - if (status == Task::recurring && - (! has ("rtype") || get ("rtype") == "")) - { - set ("rtype", "periodic"); + if (status == Task::recurring && (!has("rtype") || get("rtype") == "")) { + set("rtype", "periodic"); } // Store the derived status. - setStatus (status); + setStatus(status); #ifdef PRODUCT_TASKWARRIOR // Provide an entry date unless user already specified one. - if (! has ("entry") || get ("entry") == "") - setAsNow ("entry"); + if (!has("entry") || get("entry") == "") setAsNow("entry"); // Completed tasks need an end date, so inherit the entry date. - if ((status == Task::completed || status == Task::deleted) && - (! has ("end") || get ("end") == "")) - setAsNow ("end"); + if ((status == Task::completed || status == Task::deleted) && (!has("end") || get("end") == "")) + setAsNow("end"); // Pending tasks cannot have an end date, remove if present - if ((status == Task::pending) && (get ("end") != "")) - remove ("end"); + if ((status == Task::pending) && (get("end") != "")) remove("end"); // Provide a modified date unless user already specified one. - if (! has ("modified") || get ("modified") == "") - setAsNow ("modified"); + if (!has("modified") || get("modified") == "") setAsNow("modified"); - if (applyDefault && (! has ("parent") || get ("parent") == "")) - { + if (applyDefault && (!has("parent") || get("parent") == "")) { // Override with default.project, if not specified. - if (Task::defaultProject != "" && - ! has ("project")) - { - if (Context::getContext ().columns["project"]->validate (Task::defaultProject)) - set ("project", Task::defaultProject); + if (Task::defaultProject != "" && !has("project")) { + if (Context::getContext().columns["project"]->validate(Task::defaultProject)) + set("project", Task::defaultProject); } // Override with default.due, if not specified. - if (Task::defaultDue != "" && - ! has ("due")) - { - if (Context::getContext ().columns["due"]->validate (Task::defaultDue)) - { - Duration dur (Task::defaultDue); - if (dur.toTime_t () != 0) - set ("due", (Datetime () + dur.toTime_t ()).toEpoch ()); + if (Task::defaultDue != "" && !has("due")) { + if (Context::getContext().columns["due"]->validate(Task::defaultDue)) { + Duration dur(Task::defaultDue); + if (dur.toTime_t() != 0) + set("due", (Datetime() + dur.toTime_t()).toEpoch()); else - set ("due", Datetime (Task::defaultDue).toEpoch ()); + set("due", Datetime(Task::defaultDue).toEpoch()); } } // Override with default.scheduled, if not specified. - if (Task::defaultScheduled != "" && - ! has ("scheduled")) - { - if (Context::getContext ().columns["scheduled"]->validate (Task::defaultScheduled)) - { - Duration dur (Task::defaultScheduled); - if (dur.toTime_t () != 0) - set ("scheduled", (Datetime () + dur.toTime_t ()).toEpoch ()); + if (Task::defaultScheduled != "" && !has("scheduled")) { + if (Context::getContext().columns["scheduled"]->validate(Task::defaultScheduled)) { + Duration dur(Task::defaultScheduled); + if (dur.toTime_t() != 0) + set("scheduled", (Datetime() + dur.toTime_t()).toEpoch()); else - set ("scheduled", Datetime (Task::defaultScheduled).toEpoch ()); + set("scheduled", Datetime(Task::defaultScheduled).toEpoch()); } } // If a UDA has a default value in the configuration, // override with uda.(uda).default, if not specified. // Gather a list of all UDAs with a .default value - std::vector udas; - for (auto& var : Context::getContext ().config) - { - if (! var.first.compare (0, 4, "uda.", 4) && - var.first.find (".default") != std::string::npos) - { - auto period = var.first.find ('.', 4); - if (period != std::string::npos) - udas.push_back (var.first.substr (4, period - 4)); + std::vector udas; + for (auto& var : Context::getContext().config) { + if (!var.first.compare(0, 4, "uda.", 4) && var.first.find(".default") != std::string::npos) { + auto period = var.first.find('.', 4); + if (period != std::string::npos) udas.push_back(var.first.substr(4, period - 4)); } } - if (udas.size ()) - { + if (udas.size()) { // For each of those, setup the default value on the task now, // of course only if we don't have one on the command line already - for (auto& uda : udas) - { - std::string defVal= Context::getContext ().config.get ("uda." + uda + ".default"); + for (auto& uda : udas) { + std::string defVal = Context::getContext().config.get("uda." + uda + ".default"); // If the default is empty, or we already have a value, skip it - if (defVal != "" && get (uda) == "") - set (uda, defVal); + if (defVal != "" && get(uda) == "") set(uda, defVal); } } } @@ -1902,54 +1561,50 @@ void Task::validate (bool applyDefault /* = true */) // 2) To provide suitable warnings about odd states // Date relationships. - validate_before ("wait", "due"); - validate_before ("entry", "start"); - validate_before ("entry", "end"); - validate_before ("wait", "scheduled"); - validate_before ("scheduled", "start"); - validate_before ("scheduled", "due"); - validate_before ("scheduled", "end"); + validate_before("wait", "due"); + validate_before("entry", "start"); + validate_before("entry", "end"); + validate_before("wait", "scheduled"); + validate_before("scheduled", "start"); + validate_before("scheduled", "due"); + validate_before("scheduled", "end"); - // 3) To generate errors when the inconsistencies are not fixable + if (!has("description") || get("description") == "") + Context::getContext().footnote(format("Warning: task has no description.")); - // There is no fixing a missing description. - if (! has ("description")) - throw std::string ("A task must have a description."); - else if (get ("description") == "") - throw std::string ("Cannot add a task that is blank."); + // Cannot have an old-style recur frequency with no due date - when would it recur? + if (has("recur") && (!has("due") || get("due") == "")) { + Context::getContext().footnote(format("Warning: recurring task has no due date.")); + remove("recur"); + } - // Cannot have a recur frequency with no due date - when would it recur? - if (has ("recur") && (! has ("due") || get ("due") == "")) - throw std::string ("A recurring task must also have a 'due' date."); - - // Recur durations must be valid. - if (has ("recur")) - { - std::string value = get ("recur"); - if (value != "") - { + // Old-style recur durations must be valid. + if (has("recur")) { + std::string value = get("recur"); + if (value != "") { Duration p; std::string::size_type i = 0; - if (! p.parse (value, i)) + if (!p.parse(value, i)) { // TODO Ideal location to map unsupported old recurrence periods to supported values. - throw format ("The recurrence value '{1}' is not valid.", value); + Context::getContext().footnote( + format("Warning: The recurrence value '{1}' is not valid.", value)); + remove("recur"); + } } } } //////////////////////////////////////////////////////////////////////////////// -void Task::validate_before (const std::string& left, const std::string& right) -{ +void Task::validate_before(const std::string& left, const std::string& right) { #ifdef PRODUCT_TASKWARRIOR - if (has (left) && - has (right)) - { - Datetime date_left (get_date (left)); - Datetime date_right (get_date (right)); + if (has(left) && has(right)) { + Datetime date_left(get_date(left)); + Datetime date_right(get_date(right)); // if date is zero, then it is being removed (e.g. "due: wait:1day") - if (date_left > date_right && date_right.toEpoch () != 0) - Context::getContext ().footnote (format ("Warning: You have specified that the '{1}' date is after the '{2}' date.", left, right)); + if (date_left > date_right && date_right.toEpoch() != 0) + Context::getContext().footnote(format( + "Warning: You have specified that the '{1}' date is after the '{2}' date.", left, right)); } #endif } @@ -1958,53 +1613,24 @@ void Task::validate_before (const std::string& left, const std::string& right) // Encode values prior to serialization. // [ -> &open; // ] -> &close; -const std::string Task::encode (const std::string& value) const -{ - auto modified = str_replace (value, "[", "&open;"); - return str_replace (modified, "]", "&close;"); +const std::string Task::encode(const std::string& value) const { + auto modified = str_replace(value, "[", "&open;"); + return str_replace(modified, "]", "&close;"); } //////////////////////////////////////////////////////////////////////////////// // Decode values after parse. // [ <- &open; // ] <- &close; -const std::string Task::decode (const std::string& value) const -{ - if (value.find ('&') == std::string::npos) - return value; +const std::string Task::decode(const std::string& value) const { + if (value.find('&') == std::string::npos) return value; - auto modified = str_replace (value, "&open;", "["); - return str_replace (modified, "&close;", "]"); + auto modified = str_replace(value, "&open;", "["); + return str_replace(modified, "&close;", "]"); } //////////////////////////////////////////////////////////////////////////////// -tc::Status Task::status2tc (const Task::status status) -{ - switch (status) { - case Task::pending: return tc::Status::Pending; - case Task::completed: return tc::Status::Completed; - case Task::deleted: return tc::Status::Deleted; - case Task::waiting: return tc::Status::Pending; // waiting is no longer a status - case Task::recurring: return tc::Status::Recurring; - default: return tc::Status::Unknown; - } -} - -//////////////////////////////////////////////////////////////////////////////// -Task::status Task::tc2status (const tc::Status status) -{ - switch (status) { - case tc::Status::Pending: return Task::pending; - case tc::Status::Completed: return Task::completed; - case tc::Status::Deleted: return Task::deleted; - case tc::Status::Recurring: return Task::recurring; - default: return Task::pending; - } -} - -//////////////////////////////////////////////////////////////////////////////// -int Task::determineVersion (const std::string& line) -{ +int Task::determineVersion(const std::string& line) { // Version 2 looks like: // // uuid status [tags] [attributes] description\n @@ -2015,23 +1641,17 @@ int Task::determineVersion (const std::string& line) // // Scan for the hyphens in the uuid, the following space, and a valid status // character. - if (line[8] == '-' && - line[13] == '-' && - line[18] == '-' && - line[23] == '-' && - line[36] == ' ' && - (line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r')) - { + if (line[8] == '-' && line[13] == '-' && line[18] == '-' && line[23] == '-' && line[36] == ' ' && + (line[37] == '-' || line[37] == '+' || line[37] == 'X' || line[37] == 'r')) { // Version 3 looks like: // // uuid status [tags] [attributes] [annotations] description\n // // Scan for the number of [] pairs. - auto tagAtts = line.find ("] [", 0); - auto attsAnno = line.find ("] [", tagAtts + 1); - auto annoDesc = line.find ("] ", attsAnno + 1); - if (tagAtts != std::string::npos && - attsAnno != std::string::npos && + auto tagAtts = line.find("] [", 0); + auto attsAnno = line.find("] [", tagAtts + 1); + auto annoDesc = line.find("] ", attsAnno + 1); + if (tagAtts != std::string::npos && attsAnno != std::string::npos && annoDesc != std::string::npos) return 3; else @@ -2043,9 +1663,8 @@ int Task::determineVersion (const std::string& line) // [name:"value" ...] // // Scan for [, ] and :". - else if (line[0] == '[' && - line[line.length () - 1] == ']' && - line.find ("uuid:\"") != std::string::npos) + else if (line[0] == '[' && line[line.length() - 1] == ']' && + line.find("uuid:\"") != std::string::npos) return 4; // Version 1 looks like: @@ -2054,10 +1673,8 @@ int Task::determineVersion (const std::string& line) // X [tags] [attributes] description\n // // Scan for the first character being either the bracket or X. - else if (line.find ("X [") == 0 || - (line[0] == '[' && - line.substr (line.length () - 1, 1) != "]" && - line.length () > 3)) + else if (line.find("X [") == 0 || + (line[0] == '[' && line.substr(line.length() - 1, 1) != "]" && line.length() > 3)) return 1; // Version 5? @@ -2090,98 +1707,97 @@ int Task::determineVersion (const std::string& line) // // See rfc31-urgency.txt for full details. // -float Task::urgency_c () const -{ +float Task::urgency_c() const { float value = 0.0; #ifdef PRODUCT_TASKWARRIOR - value += fabsf (Task::urgencyProjectCoefficient) > epsilon ? (urgency_project () * Task::urgencyProjectCoefficient) : 0.0; - value += fabsf (Task::urgencyActiveCoefficient) > epsilon ? (urgency_active () * Task::urgencyActiveCoefficient) : 0.0; - value += fabsf (Task::urgencyScheduledCoefficient) > epsilon ? (urgency_scheduled () * Task::urgencyScheduledCoefficient) : 0.0; - value += fabsf (Task::urgencyWaitingCoefficient) > epsilon ? (urgency_waiting () * Task::urgencyWaitingCoefficient) : 0.0; - value += fabsf (Task::urgencyBlockedCoefficient) > epsilon ? (urgency_blocked () * Task::urgencyBlockedCoefficient) : 0.0; - value += fabsf (Task::urgencyAnnotationsCoefficient) > epsilon ? (urgency_annotations () * Task::urgencyAnnotationsCoefficient) : 0.0; - value += fabsf (Task::urgencyTagsCoefficient) > epsilon ? (urgency_tags () * Task::urgencyTagsCoefficient) : 0.0; - value += fabsf (Task::urgencyDueCoefficient) > epsilon ? (urgency_due () * Task::urgencyDueCoefficient) : 0.0; - value += fabsf (Task::urgencyBlockingCoefficient) > epsilon ? (urgency_blocking () * Task::urgencyBlockingCoefficient) : 0.0; - value += fabsf (Task::urgencyAgeCoefficient) > epsilon ? (urgency_age () * Task::urgencyAgeCoefficient) : 0.0; + value += fabsf(Task::urgencyProjectCoefficient) > epsilon + ? (urgency_project() * Task::urgencyProjectCoefficient) + : 0.0; + value += fabsf(Task::urgencyActiveCoefficient) > epsilon + ? (urgency_active() * Task::urgencyActiveCoefficient) + : 0.0; + value += fabsf(Task::urgencyScheduledCoefficient) > epsilon + ? (urgency_scheduled() * Task::urgencyScheduledCoefficient) + : 0.0; + value += fabsf(Task::urgencyWaitingCoefficient) > epsilon + ? (urgency_waiting() * Task::urgencyWaitingCoefficient) + : 0.0; + value += fabsf(Task::urgencyBlockedCoefficient) > epsilon + ? (urgency_blocked() * Task::urgencyBlockedCoefficient) + : 0.0; + value += fabsf(Task::urgencyAnnotationsCoefficient) > epsilon + ? (urgency_annotations() * Task::urgencyAnnotationsCoefficient) + : 0.0; + value += fabsf(Task::urgencyTagsCoefficient) > epsilon + ? (urgency_tags() * Task::urgencyTagsCoefficient) + : 0.0; + value += fabsf(Task::urgencyDueCoefficient) > epsilon + ? (urgency_due() * Task::urgencyDueCoefficient) + : 0.0; + value += fabsf(Task::urgencyBlockingCoefficient) > epsilon + ? (urgency_blocking() * Task::urgencyBlockingCoefficient) + : 0.0; + value += fabsf(Task::urgencyAgeCoefficient) > epsilon + ? (urgency_age() * Task::urgencyAgeCoefficient) + : 0.0; const std::string taskProjectName = get("project"); // Tag- and project-specific coefficients. - for (auto& var : Task::coefficients) - { - if (fabs (var.second) > epsilon) - { - if (! var.first.compare (0, 13, "urgency.user.", 13)) - { + for (auto& var : Task::coefficients) { + if (fabs(var.second) > epsilon) { + if (!var.first.compare(0, 13, "urgency.user.", 13)) { // urgency.user.project..coefficient auto end = std::string::npos; - if (var.first.substr (13, 8) == "project." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - std::string project = var.first.substr (21, end - 21); + if (var.first.substr(13, 8) == "project." && + (end = var.first.find(".coefficient")) != std::string::npos) { + std::string project = var.first.substr(21, end - 21); - if (taskProjectName == project || - taskProjectName.find(project + '.') == 0) - { + if (taskProjectName == project || taskProjectName.find(project + '.') == 0) { value += var.second; } } // urgency.user.tag..coefficient - if (var.first.substr (13, 4) == "tag." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - std::string tag = var.first.substr (17, end - 17); + if (var.first.substr(13, 4) == "tag." && + (end = var.first.find(".coefficient")) != std::string::npos) { + std::string tag = var.first.substr(17, end - 17); - if (hasTag (tag)) - value += var.second; + if (hasTag(tag)) value += var.second; } // urgency.user.keyword..coefficient - if (var.first.substr (13, 8) == "keyword." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - std::string keyword = var.first.substr (21, end - 21); + if (var.first.substr(13, 8) == "keyword." && + (end = var.first.find(".coefficient")) != std::string::npos) { + std::string keyword = var.first.substr(21, end - 21); - if (get ("description").find (keyword) != std::string::npos) - value += var.second; + if (get("description").find(keyword) != std::string::npos) value += var.second; } - } - else if (var.first.substr (0, 12) == "urgency.uda.") - { + } else if (var.first.substr(0, 12) == "urgency.uda.") { // urgency.uda..coefficient // urgency.uda...coefficient - auto end = var.first.find (".coefficient"); - if (end != std::string::npos) - { - const std::string uda = var.first.substr (12, end - 12); - auto dot = uda.find ('.'); - if (dot == std::string::npos) - { + auto end = var.first.find(".coefficient"); + if (end != std::string::npos) { + const std::string uda = var.first.substr(12, end - 12); + auto dot = uda.find('.'); + if (dot == std::string::npos) { // urgency.uda..coefficient - if (has (uda)) - value += var.second; - } - else - { + if (has(uda)) value += var.second; + } else { // urgency.uda...coefficient - if (get (uda.substr(0, dot)) == uda.substr(dot+1)) - value += var.second; + if (get(uda.substr(0, dot)) == uda.substr(dot + 1)) value += var.second; } } } } } - if (is_blocking && Context::getContext ().config.getBoolean ("urgency.inherit")) - { + if (is_blocking && Context::getContext().config.getBoolean("urgency.inherit")) { float prev = value; - value = std::max (value, urgency_inherit ()); + value = std::max(value, urgency_inherit()); // This is a hackish way of making sure parent tasks are sorted above // child tasks. For reports that hide blocked tasks, this is not needed. - if (prev < value) - value += 0.01; + if (prev <= value) value += 0.01; } #endif @@ -2189,11 +1805,9 @@ float Task::urgency_c () const } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency () -{ - if (recalc_urgency) - { - urgency_value = urgency_c (); +float Task::urgency() { + if (recalc_urgency) { + urgency_value = urgency_c(); // Return the sum of all terms. recalc_urgency = false; @@ -2203,16 +1817,14 @@ float Task::urgency () } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_inherit () const -{ +float Task::urgency_inherit() const { float v = -FLT_MAX; #ifdef PRODUCT_TASKWARRIOR // Calling getBlockedTasks is rather expensive. // It is called recursively for each dependency in the chain here. - for (auto& task : getBlockedTasks ()) - { + for (auto& task : getBlockedTasks()) { // Find highest urgency in all blocked tasks. - v = std::max (v, task.urgency ()); + v = std::max(v, task.urgency()); } #endif @@ -2220,71 +1832,64 @@ float Task::urgency_inherit () const } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_project () const -{ - if (has ("project")) - return 1.0; +float Task::urgency_project() const { + if (has("project")) return 1.0; return 0.0; } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_active () const -{ - if (has ("start")) - return 1.0; +float Task::urgency_active() const { + if (has("start")) return 1.0; return 0.0; } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_scheduled () const -{ - if (has ("scheduled") && - get_date ("scheduled") < time (nullptr)) - return 1.0; +float Task::urgency_scheduled() const { + if (has("scheduled") && get_date("scheduled") < time(nullptr)) return 1.0; return 0.0; } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_waiting () const -{ - if (is_waiting ()) - return 1.0; +float Task::urgency_waiting() const { + if (is_waiting()) return 1.0; return 0.0; } //////////////////////////////////////////////////////////////////////////////// // A task is blocked only if the task it depends upon is pending/waiting. -float Task::urgency_blocked () const -{ - if (is_blocked) +float Task::urgency_blocked() const { + if (is_blocked) return 1.0; + + return 0.0; +} + +//////////////////////////////////////////////////////////////////////////////// +float Task::urgency_annotations() const { + if (annotation_count >= 3) return 1.0; + else if (annotation_count == 2) + return 0.9; + else if (annotation_count == 1) + return 0.8; return 0.0; } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_annotations () const -{ - if (annotation_count >= 3) return 1.0; - else if (annotation_count == 2) return 0.9; - else if (annotation_count == 1) return 0.8; - - return 0.0; -} - -//////////////////////////////////////////////////////////////////////////////// -float Task::urgency_tags () const -{ - switch (getTagCount ()) - { - case 0: return 0.0; - case 1: return 0.8; - case 2: return 0.9; - default: return 1.0; +float Task::urgency_tags() const { + switch (getTagCount()) { + case 0: + return 0.0; + case 1: + return 0.8; + case 2: + return 0.9; + default: + return 1.0; } } @@ -2299,43 +1904,40 @@ float Task::urgency_tags () const // capped capped // // -float Task::urgency_due () const -{ - if (has ("due")) - { +float Task::urgency_due() const { + if (has("due")) { Datetime now; - Datetime due (get_date ("due")); + Datetime due(get_date("due")); // Map a range of 21 days to the value 0.2 - 1.0 float days_overdue = (now - due) / 86400.0; - if (days_overdue >= 7.0) return 1.0; // < 1 wk ago - else if (days_overdue >= -14.0) return ((days_overdue + 14.0) * 0.8 / 21.0) + 0.2; - else return 0.2; // > 2 wks + if (days_overdue >= 7.0) + return 1.0; // < 1 wk ago + else if (days_overdue >= -14.0) + return ((days_overdue + 14.0) * 0.8 / 21.0) + 0.2; + else + return 0.2; // > 2 wks } return 0.0; } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_age () const -{ - assert (has ("entry")); +float Task::urgency_age() const { + if (!has("entry")) return 1.0; Datetime now; - Datetime entry (get_date ("entry")); + Datetime entry(get_date("entry")); int age = (now - entry) / 86400; // in days - if (Task::urgencyAgeMax == 0 || age > Task::urgencyAgeMax) - return 1.0; + if (Task::urgencyAgeMax == 0 || age > Task::urgencyAgeMax) return 1.0; return (1.0 * age / Task::urgencyAgeMax); } //////////////////////////////////////////////////////////////////////////////// -float Task::urgency_blocking () const -{ - if (is_blocking) - return 1.0; +float Task::urgency_blocking() const { + if (is_blocking) return 1.0; return 0.0; } @@ -2347,169 +1949,138 @@ float Task::urgency_blocking () const // well as reducing the object depdendencies of Task. // // It came from the Command base object, but doesn't really belong there either. -void Task::modify (modType type, bool text_required /* = false */) -{ +void Task::modify(modType type, bool text_required /* = false */) { std::string label = " MODIFICATION "; // while reading the parse tree, consider DOM references in the context of // this task - auto currentTask = Context::getContext ().withCurrentTask(this); + auto currentTask = Context::getContext().withCurrentTask(this); // Need this for later comparison. - auto originalStatus = getStatus (); + auto originalStatus = getStatus(); std::string text = ""; bool mods = false; - for (auto& a : Context::getContext ().cli2._args) - { - if (a.hasTag ("MODIFICATION")) - { - if (a._lextype == Lexer::Type::pair) - { + for (auto& a : Context::getContext().cli2._args) { + if (a.hasTag("MODIFICATION")) { + if (a._lextype == Lexer::Type::pair) { // 'canonical' is the canonical name. Needs to be said. // 'value' requires eval. - std::string name = a.attribute ("canonical"); - std::string value = a.attribute ("value"); - if (value == "" || - value == "''" || - value == "\"\"") - { + std::string name = a.attribute("canonical"); + std::string value = a.attribute("value"); + if (value == "" || value == "''" || value == "\"\"") { // Special case: Handle bulk removal of 'tags' and 'depends" virtual // attributes - if (name == "depends") - { - for (auto dep: getDependencyUUIDs ()) - removeDependency(dep); - } - else if (name == "tags") - { - for (auto tag: getTags ()) - removeTag(tag); + if (name == "depends") { + for (auto dep : getDependencyUUIDs()) removeDependency(dep); + } else if (name == "tags") { + for (auto tag : getTags()) removeTag(tag); } // ::composeF4 will skip if the value is blank, but the presence of // the attribute will prevent ::validate from applying defaults. - if ((has (name) && get (name) != "") || - (name == "due" && Context::getContext ().config.has ("default.due")) || - (name == "scheduled" && Context::getContext ().config.has ("default.scheduled")) || - (name == "project" && Context::getContext ().config.has ("default.project"))) - { + if ((has(name) && get(name) != "") || + (name == "due" && Context::getContext().config.has("default.due")) || + (name == "scheduled" && Context::getContext().config.has("default.scheduled")) || + (name == "project" && Context::getContext().config.has("default.project"))) { mods = true; - set (name, ""); + set(name, ""); } - Context::getContext ().debug (label + name + " <-- ''"); - } - else - { - Lexer::dequote (value); + Context::getContext().debug(label + name + " <-- ''"); + } else { + Lexer::dequote(value); // Get the column info. Some columns are not modifiable. - Column* column = Context::getContext ().columns[name]; - if (! column || - ! column->modifiable ()) - throw format ("The '{1}' attribute does not allow a value of '{2}'.", name, value); + Column* column = Context::getContext().columns[name]; + if (!column || !column->modifiable()) + throw format("The '{1}' attribute does not allow a value of '{2}'.", name, value); // Delegate modification to the column object or their base classes. - if (name == "depends" || - name == "tags" || - name == "recur" || - column->type () == "date" || - column->type () == "duration" || - column->type () == "numeric" || - column->type () == "string") - { - column->modify (*this, value); + if (name == "depends" || name == "tags" || name == "recur" || column->type() == "date" || + column->type() == "duration" || column->type() == "numeric" || + column->type() == "string" || column->type() == "uuid") { + column->modify(*this, value); mods = true; } else - throw format ("Unrecognized column type '{1}' for column '{2}'", column->type (), name); + throw format("Unrecognized column type '{1}' for column '{2}'", column->type(), name); } } // Perform description/annotation substitution. - else if (a._lextype == Lexer::Type::substitution) - { - Context::getContext ().debug (label + "substitute " + a.attribute ("raw")); - substitute (a.attribute ("from"), - a.attribute ("to"), - a.attribute ("flags")); + else if (a._lextype == Lexer::Type::substitution) { + Context::getContext().debug(label + "substitute " + a.attribute("raw")); + substitute(a.attribute("from"), a.attribute("to"), a.attribute("flags")); mods = true; } // Tags need special handling because they are essentially a vector stored // in a single string, therefore Task::{add,remove}Tag must be called as // appropriate. - else if (a._lextype == Lexer::Type::tag) - { - std::string tag = a.attribute ("name"); - feedback_reserved_tags (tag); + else if (a._lextype == Lexer::Type::tag) { + std::string tag = a.attribute("name"); + feedback_reserved_tags(tag); - if (a.attribute ("sign") == "+") - { - Context::getContext ().debug (label + "tags <-- add '" + tag + '\''); - addTag (tag); - feedback_special_tags (*this, tag); - } - else - { - Context::getContext ().debug (label + "tags <-- remove '" + tag + '\''); - removeTag (tag); + if (a.attribute("sign") == "+") { + Context::getContext().debug(label + "tags <-- add '" + tag + '\''); + addTag(tag); + feedback_special_tags(*this, tag); + } else { + Context::getContext().debug(label + "tags <-- remove '" + tag + '\''); + removeTag(tag); } mods = true; } // Unknown args are accumulated as though they were WORDs. - else - { - if (text != "") - text += ' '; - text += a.attribute ("raw"); + else { + if (text != "") text += ' '; + text += a.attribute("raw"); } } } // Task::modType determines what happens to the WORD arguments, if there are // any. - if (text != "") - { - Lexer::dequote (text); + if (text != "") { + Lexer::dequote(text); - switch (type) - { - case modReplace: - Context::getContext ().debug (label + "description <-- '" + text + '\''); - set ("description", text); - break; + switch (type) { + case modReplace: + Context::getContext().debug(label + "description <-- '" + text + '\''); + set("description", text); + break; - case modPrepend: - Context::getContext ().debug (label + "description <-- '" + text + "' + description"); - set ("description", text + ' ' + get ("description")); - break; + case modPrepend: + Context::getContext().debug(label + "description <-- '" + text + "' + description"); + set("description", text + ' ' + get("description")); + break; - case modAppend: - Context::getContext ().debug (label + "description <-- description + '" + text + '\''); - set ("description", get ("description") + ' ' + text); - break; + case modAppend: + Context::getContext().debug(label + "description <-- description + '" + text + '\''); + set("description", get("description") + ' ' + text); + break; - case modAnnotate: - Context::getContext ().debug (label + "new annotation <-- '" + text + '\''); - addAnnotation (text); - break; + case modAnnotate: + Context::getContext().debug(label + "new annotation <-- '" + text + '\''); + addAnnotation(text); + break; } - } - else if (! mods && text_required) - throw std::string ("Additional text must be provided."); + } else if (!mods && text_required) + throw std::string("Additional text must be provided."); // Modifying completed/deleted tasks generates a message, if the modification // does not change status. - if ((getStatus () == Task::completed || getStatus () == Task::deleted) && - getStatus () == originalStatus) - { - auto uuid = get ("uuid").substr (0, 8); - Context::getContext ().footnote (format ("Note: Modified task {1} is {2}. You may wish to make this task pending with: task {3} modify status:pending", uuid, get ("status"), uuid)); + if ((getStatus() == Task::completed || getStatus() == Task::deleted) && + getStatus() == originalStatus) { + auto uuid = get("uuid").substr(0, 8); + Context::getContext().footnote( + format("Note: Modified task {1} is {2}. You may wish to make this task pending with: task " + "{3} modify status:pending", + uuid, get("status"), uuid)); } } #endif @@ -2517,450 +2088,75 @@ void Task::modify (modType type, bool text_required /* = false */) //////////////////////////////////////////////////////////////////////////////// // Compare this task to another and summarize the differences for display, in // the future tense ("Foo will be set to .."). -std::string Task::diff (const Task& after) const -{ +std::string Task::diff(const Task& after) const { // Attributes are all there is, so figure the different attribute names // between this (before) and after. - std::vector beforeAtts; - for (auto& att : data) - beforeAtts.push_back (att.first); + std::vector beforeAtts; + for (auto& att : data) beforeAtts.push_back(att.first); - std::vector afterAtts; - for (auto& att : after.data) - afterAtts.push_back (att.first); + std::vector afterAtts; + for (auto& att : after.data) afterAtts.push_back(att.first); - std::vector beforeOnly; - std::vector afterOnly; - listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); + std::vector beforeOnly; + std::vector afterOnly; + listDiff(beforeAtts, afterAtts, beforeOnly, afterOnly); // Now start generating a description of the differences. std::stringstream out; - for (auto& name : beforeOnly) - { - if (isAnnotationAttr (name)) - { - out << " - " - << format ("Annotation {1} will be removed.", name) - << "\n"; - } - else if (isTagAttr (name)) - { - out << " - " - << format ("Tag {1} will be removed.", attr2Tag (name)) - << "\n"; - } - else if (isDepAttr (name)) - { - out << " - " - << format ("Depenency on {1} will be removed.", attr2Dep (name)) - << "\n"; - } - else if (name == "depends" || name == "tags") - { + for (auto& name : beforeOnly) { + if (isAnnotationAttr(name)) { + out << " - " << format("Annotation {1} will be removed.", name) << "\n"; + } else if (isTagAttr(name)) { + out << " - " << format("Tag {1} will be removed.", attr2Tag(name)) << "\n"; + } else if (isDepAttr(name)) { + out << " - " << format("Depenency on {1} will be removed.", attr2Dep(name)) << "\n"; + } else if (name == "depends" || name == "tags") { // do nothing for legacy attributes - } - else - { - out << " - " - << format ("{1} will be deleted.", Lexer::ucFirst (name)) - << "\n"; + } else { + out << " - " << format("{1} will be deleted.", Lexer::ucFirst(name)) << "\n"; } } - for (auto& name : afterOnly) - { - if (isAnnotationAttr (name)) - { - out << format ("Annotation of {1} will be added.\n", after.get (name)); - } - else if (isTagAttr (name)) - { - out << format ("Tag {1} will be added.\n", attr2Tag (name)); - } - else if (isDepAttr (name)) - { - out << format ("Dependency on {1} will be added.\n", attr2Dep (name)); - } - else if (name == "depends" || name == "tags") - { + for (auto& name : afterOnly) { + if (isAnnotationAttr(name)) { + out << format("Annotation of {1} will be added.\n", after.get(name)); + } else if (isTagAttr(name)) { + out << format("Tag {1} will be added.\n", attr2Tag(name)); + } else if (isDepAttr(name)) { + out << format("Dependency on {1} will be added.\n", attr2Dep(name)); + } else if (name == "depends" || name == "tags") { // do nothing for legacy attributes - } - else + } else out << " - " - << format ("{1} will be set to '{2}'.", - Lexer::ucFirst (name), - renderAttribute (name, after.get (name))) + << format("{1} will be set to '{2}'.", Lexer::ucFirst(name), + renderAttribute(name, after.get(name))) << "\n"; } - for (auto& name : beforeAtts) - { + for (auto& name : beforeAtts) { // Ignore UUID differences, and find values that changed, but are not also // in the beforeOnly and afterOnly lists, which have been handled above.. - if (name != "uuid" && - get (name) != after.get (name) && - std::find (beforeOnly.begin (), beforeOnly.end (), name) == beforeOnly.end () && - std::find (afterOnly.begin (), afterOnly.end (), name) == afterOnly.end ()) - { - if (name == "depends" || name == "tags") - { + if (name != "uuid" && get(name) != after.get(name) && + std::find(beforeOnly.begin(), beforeOnly.end(), name) == beforeOnly.end() && + std::find(afterOnly.begin(), afterOnly.end(), name) == afterOnly.end()) { + if (name == "depends" || name == "tags") { // do nothing for legacy attributes - } - else if (isTagAttr (name) || isDepAttr (name)) - { + } else if (isTagAttr(name) || isDepAttr(name)) { // ignore new attributes - } - else if (isAnnotationAttr (name)) - { - out << format ("Annotation will be changed to {1}.\n", after.get (name)); - } - else + } else if (isAnnotationAttr(name)) { + out << format("Annotation will be changed to {1}.\n", after.get(name)); + } else out << " - " - << format ("{1} will be changed from '{2}' to '{3}'.", - Lexer::ucFirst (name), - renderAttribute (name, get (name)), - renderAttribute (name, after.get (name))) + << format("{1} will be changed from '{2}' to '{3}'.", Lexer::ucFirst(name), + renderAttribute(name, get(name)), renderAttribute(name, after.get(name))) << "\n"; } } // Shouldn't just say nothing. - if (out.str ().length () == 0) - out << " - No changes will be made.\n"; + if (out.str().length() == 0) out << " - No changes will be made.\n"; - return out.str (); -} - -//////////////////////////////////////////////////////////////////////////////// -// Similar to diff, but formatted for inclusion in the output of the info command -std::string Task::diffForInfo ( - const Task& after, - const std::string& dateformat, - long& last_timestamp, - const long current_timestamp) const -{ - // Attributes are all there is, so figure the different attribute names - // between before and after. - std::vector beforeAtts; - for (auto& att : data) - beforeAtts.push_back (att.first); - - std::vector afterAtts; - for (auto& att : after.data) - afterAtts.push_back (att.first); - - std::vector beforeOnly; - std::vector afterOnly; - listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); - - // Now start generating a description of the differences. - std::stringstream out; - for (auto& name : beforeOnly) - { - if (isAnnotationAttr (name)) - { - out << format ("Annotation '{1}' deleted.\n", get (name)); - } - else if (isTagAttr (name)) - { - out << format ("Tag '{1}' deleted.\n", attr2Tag(name)); - } - else if (isDepAttr (name)) - { - out << format ("Dependency on '{1}' deleted.\n", attr2Dep(name)); - } - else if (name == "depends" || name == "tags") - { - // do nothing for legacy attributes - } - else if (name == "start") - { - Datetime started (get ("start")); - Datetime stopped; - - if (after.has ("end")) - // Task was marked as finished, use end time - stopped = Datetime (after.get ("end")); - else - // Start attribute was removed, use modification time - stopped = Datetime (current_timestamp); - - out << format ("{1} deleted (duration: {2}).", - Lexer::ucFirst (name), - Duration (stopped - started).format ()) - << "\n"; - } - else - { - out << format ("{1} deleted.\n", Lexer::ucFirst (name)); - } - } - - for (auto& name : afterOnly) - { - if (isAnnotationAttr (name)) - { - out << format ("Annotation of '{1}' added.\n", after.get (name)); - } - else if (isTagAttr (name)) - { - out << format ("Tag '{1}' added.\n", attr2Tag (name)); - } - else if (isDepAttr (name)) - { - out << format ("Dependency on '{1}' added.\n", attr2Dep (name)); - } - else if (name == "depends" || name == "tags") - { - // do nothing for legacy attributes - } - else - { - if (name == "start") - last_timestamp = current_timestamp; - - out << format ("{1} set to '{2}'.", - Lexer::ucFirst (name), - renderAttribute (name, after.get (name), dateformat)) - << "\n"; - } - } - - for (auto& name : beforeAtts) - if (name != "uuid" && - name != "modified" && - get (name) != after.get (name) && - get (name) != "" && - after.get (name) != "") - { - if (name == "depends" || name == "tags") - { - // do nothing for legacy attributes - } - else if (isTagAttr (name) || isDepAttr (name)) - { - // ignore new attributes - } - else if (isAnnotationAttr (name)) - { - out << format ("Annotation changed to '{1}'.\n", after.get (name)); - } - else - out << format ("{1} changed from '{2}' to '{3}'.", - Lexer::ucFirst (name), - renderAttribute (name, get (name), dateformat), - renderAttribute (name, after.get (name), dateformat)) - << "\n"; - } - - // Shouldn't just say nothing. - if (out.str ().length () == 0) - out << "No changes made.\n"; - - return out.str (); -} - -//////////////////////////////////////////////////////////////////////////////// -// Similar to diff, but formatted as a side-by-side table for an Undo preview -Table Task::diffForUndoSide ( - const Task& after) const -{ - // Set the colors. - Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : ""); - Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : ""); - - // Attributes are all there is, so figure the different attribute names - // between before and after. - Table view; - view.width (Context::getContext ().getWidth ()); - view.intraPadding (2); - view.add (""); - view.add ("Prior Values"); - view.add ("Current Values"); - setHeaderUnderline (view); - - if (!is_empty ()) - { - const Task &before = *this; - - std::vector beforeAtts; - for (auto& att : before.data) - beforeAtts.push_back (att.first); - - std::vector afterAtts; - for (auto& att : after.data) - afterAtts.push_back (att.first); - - std::vector beforeOnly; - std::vector afterOnly; - listDiff (beforeAtts, afterAtts, beforeOnly, afterOnly); - - int row; - for (auto& name : beforeOnly) - { - row = view.addRow (); - view.set (row, 0, name); - view.set (row, 1, renderAttribute (name, before.get (name)), color_red); - } - - for (auto& att : before.data) - { - std::string priorValue = before.get (att.first); - std::string currentValue = after.get (att.first); - - if (currentValue != "") - { - row = view.addRow (); - view.set (row, 0, att.first); - view.set (row, 1, renderAttribute (att.first, priorValue), - (priorValue != currentValue ? color_red : Color ())); - view.set (row, 2, renderAttribute (att.first, currentValue), - (priorValue != currentValue ? color_green : Color ())); - } - } - - for (auto& name : afterOnly) - { - row = view.addRow (); - view.set (row, 0, name); - view.set (row, 2, renderAttribute (name, after.get (name)), color_green); - } - } - else - { - int row; - for (auto& att : after.data) - { - row = view.addRow (); - view.set (row, 0, att.first); - view.set (row, 2, renderAttribute (att.first, after.get (att.first)), color_green); - } - } - - return view; -} - -//////////////////////////////////////////////////////////////////////////////// -// Similar to diff, but formatted as a diff for an Undo preview -Table Task::diffForUndoPatch ( - const Task& after, - const Datetime& lastChange) const -{ - // This style looks like this: - // --- before 2009-07-04 00:00:25.000000000 +0200 - // +++ after 2009-07-04 00:00:45.000000000 +0200 - // - // - name: old // att deleted - // + name: - // - // - name: old // att changed - // + name: new - // - // - name: - // + name: new // att added - // - - // Set the colors. - Color color_red (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.before") : ""); - Color color_green (Context::getContext ().color () ? Context::getContext ().config.get ("color.undo.after") : ""); - - const Task &before = *this; - - // Generate table header. - Table view; - view.width (Context::getContext ().getWidth ()); - view.intraPadding (2); - view.add (""); - view.add (""); - - int row = view.addRow (); - view.set (row, 0, "--- previous state", color_red); - view.set (row, 1, "Undo will restore this state", color_red); - - row = view.addRow (); - view.set (row, 0, "+++ current state ", color_green); - view.set (row, 1, format ("Change made {1}", - lastChange.toString (Context::getContext ().config.get ("dateformat"))), - color_green); - - view.addRow (); - - // Add rows to table showing diffs. - std::vector all = Context::getContext ().getColumns (); - - // Now factor in the annotation attributes. - for (auto& it : before.data) - if (it.first.substr (0, 11) == "annotation_") - all.push_back (it.first); - - for (auto& it : after.data) - if (it.first.substr (0, 11) == "annotation_") - all.push_back (it.first); - - // Now render all the attributes. - std::sort (all.begin (), all.end ()); - - std::string before_att; - std::string after_att; - std::string last_att; - for (auto& a : all) - { - if (a != last_att) // Skip duplicates. - { - last_att = a; - - before_att = before.get (a); - after_att = after.get (a); - - // Don't report different uuid. - // Show nothing if values are the unchanged. - if (a == "uuid" || - before_att == after_att) - { - // Show nothing - no point displaying that which did not change. - - // row = view.addRow (); - // view.set (row, 0, *a + ":"); - // view.set (row, 1, before_att); - } - - // Attribute deleted. - else if (before_att != "" && after_att == "") - { - row = view.addRow (); - view.set (row, 0, '-' + a + ':', color_red); - view.set (row, 1, before_att, color_red); - - row = view.addRow (); - view.set (row, 0, '+' + a + ':', color_green); - } - - // Attribute added. - else if (before_att == "" && after_att != "") - { - row = view.addRow (); - view.set (row, 0, '-' + a + ':', color_red); - - row = view.addRow (); - view.set (row, 0, '+' + a + ':', color_green); - view.set (row, 1, after_att, color_green); - } - - // Attribute changed. - else - { - row = view.addRow (); - view.set (row, 0, '-' + a + ':', color_red); - view.set (row, 1, before_att, color_red); - - row = view.addRow (); - view.set (row, 0, '+' + a + ':', color_green); - view.set (row, 1, after_att, color_green); - } - } - } - - return view; + return out.str(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Task.h b/src/Task.h index eb7b2b71f..0b577baff 100644 --- a/src/Task.h +++ b/src/Task.h @@ -27,27 +27,26 @@ #ifndef INCLUDED_TASK #define INCLUDED_TASK -#include -#include -#include -#include -#include +#include #include #include -#include -#include +#include +#include -class Task -{ -public: +#include +#include +#include + +class Task { + public: static std::string defaultProject; static std::string defaultDue; static std::string defaultScheduled; static bool searchCaseSensitive; static bool regex; - static std::map attributes; // name -> type - static std::map coefficients; - static std::map > customOrder; + static std::map attributes; // name -> type + static std::map coefficients; + static std::map> customOrder; static float urgencyProjectCoefficient; static float urgencyActiveCoefficient; static float urgencyScheduledCoefficient; @@ -60,160 +59,155 @@ public: static float urgencyAgeCoefficient; static float urgencyAgeMax; -public: - Task () = default; - bool operator== (const Task&); - bool operator!= (const Task&); - Task (const std::string&); - Task (const json::object*); - Task (tc::Task); + public: + Task() = default; + bool operator==(const Task&); + bool operator!=(const Task&); + Task(const std::string&); + Task(const json::object*); + Task(rust::Box); - void parse (const std::string&); - std::string composeF4 () const; - std::string composeJSON (bool decorate = false) const; + void parse(const std::string&); + std::string composeJSON(bool decorate = false); // Status values. - enum status {pending, completed, deleted, recurring, waiting}; + enum status { pending, completed, deleted, recurring, waiting }; // Date state values. - enum dateState {dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday}; + enum dateState { dateNotDue, dateAfterToday, dateLaterToday, dateEarlierToday, dateBeforeToday }; // Public data. - int id {0}; - float urgency_value {0.0}; - bool recalc_urgency {true}; - bool is_blocked {false}; - bool is_blocking {false}; - int annotation_count {0}; + int id{0}; + float urgency_value{0.0}; + bool recalc_urgency{true}; + bool is_blocked{false}; + bool is_blocking{false}; + int annotation_count{0}; // Series of helper functions. - static status textToStatus (const std::string&); - static std::string statusToText (status); - static tc::Status status2tc (const Task::status); - static Task::status tc2status (const tc::Status); + static status textToStatus(const std::string&); + static std::string statusToText(status); - void setAsNow (const std::string&); - bool has (const std::string&) const; - std::vector all () const; - const std::string identifier (bool shortened = false) const; - const std::string get (const std::string&) const; - const std::string& get_ref (const std::string&) const; - int get_int (const std::string&) const; - unsigned long get_ulong (const std::string&) const; - float get_float (const std::string&) const; - time_t get_date (const std::string&) const; - void set (const std::string&, const std::string&); - void set (const std::string&, long long); - void remove (const std::string&); + void setAsNow(const std::string&); + bool has(const std::string&) const; + std::vector all() const; + const std::string identifier(bool shortened = false) const; + const std::string get(const std::string&) const; + const std::string& get_ref(const std::string&) const; + int get_int(const std::string&) const; + unsigned long get_ulong(const std::string&) const; + float get_float(const std::string&) const; + time_t get_date(const std::string&) const; + void set(const std::string&, const std::string&); + void set(const std::string&, long long); + void remove(const std::string&); - bool is_empty () const; + bool is_empty() const; #ifdef PRODUCT_TASKWARRIOR - bool is_ready () const; - bool is_due () const; - bool is_dueyesterday () const; - bool is_duetoday () const; - bool is_duetomorrow () const; - bool is_dueweek () const; - bool is_duemonth () const; - bool is_duequarter () const; - bool is_dueyear () const; - bool is_overdue () const; - bool is_udaPresent () const; - bool is_orphanPresent () const; + bool is_ready() const; + bool is_due() const; + bool is_dueyesterday() const; + bool is_duetoday() const; + bool is_duetomorrow() const; + bool is_dueweek() const; + bool is_duemonth() const; + bool is_duequarter() const; + bool is_dueyear() const; + bool is_overdue() const; + bool is_udaPresent() const; + bool is_orphanPresent() const; - static bool isTagAttr (const std::string&); - static bool isDepAttr (const std::string&); - static bool isAnnotationAttr (const std::string&); + static bool isTagAttr(const std::string&); + static bool isDepAttr(const std::string&); + static bool isAnnotationAttr(const std::string&); #endif - bool is_waiting () const; + bool is_waiting() const; - status getStatus () const; - void setStatus (status); + status getStatus() const; + void setStatus(status); #ifdef PRODUCT_TASKWARRIOR - dateState getDateState (const std::string&) const; + dateState getDateState(const std::string&) const; #endif - int getTagCount () const; - bool hasTag (const std::string&) const; - void addTag (const std::string&); - void setTags (const std::vector &); - std::vector getTags () const; - void removeTag (const std::string&); + int getTagCount() const; + bool hasTag(const std::string&) const; + void addTag(const std::string&); + void setTags(const std::vector&); + std::vector getTags() const; + void removeTag(const std::string&); - int getAnnotationCount () const; - bool hasAnnotations () const; - std::map getAnnotations () const; - void setAnnotations (const std::map &); - void addAnnotation (const std::string&); - void removeAnnotations (); + int getAnnotationCount() const; + bool hasAnnotations() const; + std::map getAnnotations() const; + void setAnnotations(const std::map&); + void addAnnotation(const std::string&); + void removeAnnotations(); #ifdef PRODUCT_TASKWARRIOR - void addDependency (int); + void addDependency(int); #endif - void addDependency (const std::string&); + void addDependency(const std::string&); #ifdef PRODUCT_TASKWARRIOR - void removeDependency (int); - void removeDependency (const std::string&); - bool hasDependency (const std::string&) const; - std::vector getDependencyIDs () const; - std::vector getDependencyUUIDs () const; - std::vector getBlockedTasks () const; - std::vector getDependencyTasks () const; + void removeDependency(int); + void removeDependency(const std::string&); + bool hasDependency(const std::string&) const; + std::vector getDependencyIDs() const; + std::vector getDependencyUUIDs() const; + std::vector getBlockedTasks() const; + std::vector getDependencyTasks() const; - std::vector getUDAOrphans () const; + std::vector getUDAOrphans() const; - void substitute (const std::string&, const std::string&, const std::string&); + void substitute(const std::string&, const std::string&, const std::string&); #endif - void validate (bool applyDefault = true); + static std::string tag2Attr(const std::string&); + static std::string attr2Tag(const std::string&); + static std::string dep2Attr(const std::string&); + static std::string attr2Dep(const std::string&); - float urgency_c () const; - float urgency (); + void validate_add(); + void validate(bool applyDefault = true); + + float urgency_c() const; + float urgency(); #ifdef PRODUCT_TASKWARRIOR - enum modType {modReplace, modPrepend, modAppend, modAnnotate}; - void modify (modType, bool text_required = false); + enum modType { modReplace, modPrepend, modAppend, modAnnotate }; + void modify(modType, bool text_required = false); #endif - std::string diff (const Task& after) const; - std::string diffForInfo (const Task& after, const std::string& dateformat, long& last_timestamp, const long current_timestamp) const; - Table diffForUndoSide (const Task& after) const; - Table diffForUndoPatch (const Task& after, const Datetime& lastChange) const; + std::string diff(const Task& after) const; + private: + int determineVersion(const std::string&); + void parseJSON(const std::string&); + void parseJSON(const json::object*); + void parseTC(rust::Box); + void parseLegacy(const std::string&); + void validate_before(const std::string&, const std::string&); + const std::string encode(const std::string&) const; + const std::string decode(const std::string&) const; + void fixDependsAttribute(); + void fixTagsAttribute(); -private: - int determineVersion (const std::string&); - void parseJSON (const std::string&); - void parseJSON (const json::object*); - void parseTC (const tc::Task&); - void parseLegacy (const std::string&); - void validate_before (const std::string&, const std::string&); - const std::string encode (const std::string&) const; - const std::string decode (const std::string&) const; - const std::string tag2Attr (const std::string&) const; - const std::string attr2Tag (const std::string&) const; - const std::string dep2Attr (const std::string&) const; - const std::string attr2Dep (const std::string&) const; - void fixDependsAttribute (); - void fixTagsAttribute (); + protected: + std::map data{}; -protected: - std::map data {}; - -public: - float urgency_project () const; - float urgency_active () const; - float urgency_scheduled () const; - float urgency_waiting () const; - float urgency_blocked () const; - float urgency_inherit () const; - float urgency_annotations () const; - float urgency_tags () const; - float urgency_due () const; - float urgency_blocking () const; - float urgency_age () const; + public: + float urgency_project() const; + float urgency_active() const; + float urgency_scheduled() const; + float urgency_waiting() const; + float urgency_blocked() const; + float urgency_inherit() const; + float urgency_annotations() const; + float urgency_tags() const; + float urgency_due() const; + float urgency_blocking() const; + float urgency_age() const; }; #endif diff --git a/src/Variant.cpp b/src/Variant.cpp index 84c0a645e..ef0a08ac3 100644 --- a/src/Variant.cpp +++ b/src/Variant.cpp @@ -25,927 +25,945 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include #include #include #include +#include +#include #include +#include +#include + +#include +#include // These are all error messages generated by the expression evaluator, and are // mostly concerned with how various operators interact with the different // data types. -#define STRING_VARIANT_TIME_T "Cannot instantiate this type with a time_t value." -#define STRING_VARIANT_EXP_BOOL "Cannot exponentiate Booleans" -#define STRING_VARIANT_EXP_NON_INT "Cannot exponentiate to a non-integer power" -#define STRING_VARIANT_EXP_STRING "Cannot exponentiate strings" -#define STRING_VARIANT_EXP_DATE "Cannot exponentiate dates" -#define STRING_VARIANT_EXP_DURATION "Cannot exponentiate durations" -#define STRING_VARIANT_SUB_BOOL "Cannot subtract from a Boolean value" -#define STRING_VARIANT_SUB_STRING "Cannot subtract strings" -#define STRING_VARIANT_SUB_DATE "Cannot subtract a date" -#define STRING_VARIANT_ADD_BOOL "Cannot add two Boolean values" -#define STRING_VARIANT_ADD_DATE "Cannot add two date values" -#define STRING_VARIANT_MUL_BOOL "Cannot multiply Boolean values" -#define STRING_VARIANT_MUL_DATE "Cannot multiply date values" -#define STRING_VARIANT_MUL_REAL_STR "Cannot multiply real numbers by strings" -#define STRING_VARIANT_MUL_STR_REAL "Cannot multiply strings by real numbers" -#define STRING_VARIANT_MUL_STR_STR "Cannot multiply strings by strings" -#define STRING_VARIANT_MUL_STR_DATE "Cannot multiply strings by dates" -#define STRING_VARIANT_MUL_STR_DUR "Cannot multiply strings by durations" -#define STRING_VARIANT_MUL_DUR_STR "Cannot multiply durations by strings" -#define STRING_VARIANT_MUL_DUR_DATE "Cannot multiply durations by dates" -#define STRING_VARIANT_MUL_DUR_DUR "Cannot multiply durations by durations" -#define STRING_VARIANT_DIV_BOOL "Cannot divide Boolean values" -#define STRING_VARIANT_DIV_INT_BOOL "Cannot divide integers by Boolean values" -#define STRING_VARIANT_DIV_ZERO "Cannot divide by zero" -#define STRING_VARIANT_DIV_INT_STR "Cannot divide integer by string" -#define STRING_VARIANT_DIV_INT_DATE "Cannot divide integer by date values" +#define STRING_VARIANT_TIME_T "Cannot instantiate this type with a time_t value." +#define STRING_VARIANT_EXP_BOOL "Cannot exponentiate Booleans" +#define STRING_VARIANT_EXP_NON_INT "Cannot exponentiate to a non-integer power" +#define STRING_VARIANT_EXP_STRING "Cannot exponentiate strings" +#define STRING_VARIANT_EXP_DATE "Cannot exponentiate dates" +#define STRING_VARIANT_EXP_DURATION "Cannot exponentiate durations" +#define STRING_VARIANT_SUB_BOOL "Cannot subtract from a Boolean value" +#define STRING_VARIANT_SUB_STRING "Cannot subtract strings" +#define STRING_VARIANT_SUB_DATE "Cannot subtract a date" +#define STRING_VARIANT_ADD_BOOL "Cannot add two Boolean values" +#define STRING_VARIANT_ADD_DATE "Cannot add two date values" +#define STRING_VARIANT_MUL_BOOL "Cannot multiply Boolean values" +#define STRING_VARIANT_MUL_DATE "Cannot multiply date values" +#define STRING_VARIANT_MUL_REAL_STR "Cannot multiply real numbers by strings" +#define STRING_VARIANT_MUL_STR_REAL "Cannot multiply strings by real numbers" +#define STRING_VARIANT_MUL_STR_STR "Cannot multiply strings by strings" +#define STRING_VARIANT_MUL_STR_DATE "Cannot multiply strings by dates" +#define STRING_VARIANT_MUL_STR_DUR "Cannot multiply strings by durations" +#define STRING_VARIANT_MUL_DUR_STR "Cannot multiply durations by strings" +#define STRING_VARIANT_MUL_DUR_DATE "Cannot multiply durations by dates" +#define STRING_VARIANT_MUL_DUR_DUR "Cannot multiply durations by durations" +#define STRING_VARIANT_DIV_BOOL "Cannot divide Boolean values" +#define STRING_VARIANT_DIV_INT_BOOL "Cannot divide integers by Boolean values" +#define STRING_VARIANT_DIV_ZERO "Cannot divide by zero" +#define STRING_VARIANT_DIV_INT_STR "Cannot divide integer by string" +#define STRING_VARIANT_DIV_INT_DATE "Cannot divide integer by date values" #define STRING_VARIANT_DIV_REAL_BOOL "Cannot divide real by Boolean" -#define STRING_VARIANT_DIV_REAL_STR "Cannot divide real numbers by strings" +#define STRING_VARIANT_DIV_REAL_STR "Cannot divide real numbers by strings" #define STRING_VARIANT_DIV_REAL_DATE "Cannot divide real numbers by dates" -#define STRING_VARIANT_DIV_DUR_BOOL "Cannot divide duration by Boolean" -#define STRING_VARIANT_DIV_DUR_STR "Cannot divide durations by strings" -#define STRING_VARIANT_DIV_DUR_DATE "Cannot divide durations by dates" -#define STRING_VARIANT_DIV_DUR_DUR "Cannot divide durations by durations" -#define STRING_VARIANT_MOD_BOOL "Cannot modulo Booleans" -#define STRING_VARIANT_MOD_DATE "Cannot modulo date values" -#define STRING_VARIANT_MOD_DUR "Cannot modulo duration values" -#define STRING_VARIANT_MOD_INT_BOOL "Cannot modulo integer by Boolean" -#define STRING_VARIANT_MOD_INT_DATE "Cannot modulo integer by date values" -#define STRING_VARIANT_MOD_INT_DUR "Cannot modulo integer by duration values" -#define STRING_VARIANT_MOD_INT_STR "Cannot modulo integer by string" +#define STRING_VARIANT_DIV_DUR_BOOL "Cannot divide duration by Boolean" +#define STRING_VARIANT_DIV_DUR_STR "Cannot divide durations by strings" +#define STRING_VARIANT_DIV_DUR_DATE "Cannot divide durations by dates" +#define STRING_VARIANT_MOD_BOOL "Cannot modulo Booleans" +#define STRING_VARIANT_MOD_DATE "Cannot modulo date values" +#define STRING_VARIANT_MOD_DUR "Cannot modulo duration values" +#define STRING_VARIANT_MOD_INT_BOOL "Cannot modulo integer by Boolean" +#define STRING_VARIANT_MOD_INT_DATE "Cannot modulo integer by date values" +#define STRING_VARIANT_MOD_INT_DUR "Cannot modulo integer by duration values" +#define STRING_VARIANT_MOD_INT_STR "Cannot modulo integer by string" #define STRING_VARIANT_MOD_REAL_BOOL "Cannot modulo real by Boolean" -#define STRING_VARIANT_MOD_REAL_DUR "Cannot modulo real by duration values" +#define STRING_VARIANT_MOD_REAL_DUR "Cannot modulo real by duration values" #define STRING_VARIANT_MOD_REAL_DATE "Cannot modulo real numbers by dates" -#define STRING_VARIANT_MOD_REAL_STR "Cannot modulo real numbers by strings" -#define STRING_VARIANT_MOD_STR "Cannot modulo string values" -#define STRING_VARIANT_MOD_ZERO "Cannot modulo zero" -#define STRING_VARIANT_SQRT_NEG "Cannot take the square root of a negative number." +#define STRING_VARIANT_MOD_REAL_STR "Cannot modulo real numbers by strings" +#define STRING_VARIANT_MOD_STR "Cannot modulo string values" +#define STRING_VARIANT_MOD_ZERO "Cannot modulo zero" +#define STRING_VARIANT_SQRT_NEG "Cannot take the square root of a negative number." std::string Variant::dateFormat = ""; bool Variant::searchCaseSensitive = true; bool Variant::searchUsingRegex = true; //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const Variant& other) -{ - _type = other._type; - _bool = other._bool; - _integer = other._integer; - _real = other._real; - _string = other._string; - _date = other._date; +Variant::Variant(const Variant& other) { + _type = other._type; + _bool = other._bool; + _integer = other._integer; + _real = other._real; + _string = other._string; + _date = other._date; _duration = other._duration; - _source = other._source; + _source = other._source; } //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const bool value) -: _type (Variant::type_boolean) -, _bool (value) -{ -} +Variant::Variant(const bool value) : _type(Variant::type_boolean), _bool(value) {} //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const int value) -: _type (Variant::type_integer) -, _integer (value) -{ -} +Variant::Variant(const int value) : _type(Variant::type_integer), _integer(value) {} //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const double value) -: _type (Variant::type_real) -, _real (value) -{ -} +Variant::Variant(const double value) : _type(Variant::type_real), _real(value) {} //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const std::string& value) -: _type (Variant::type_string) -, _string (value) -{ -} +Variant::Variant(const std::string& value) : _type(Variant::type_string), _string(value) {} //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const char* value) -: _type (Variant::type_string) -, _string (value) -{ -} +Variant::Variant(const char* value) : _type(Variant::type_string), _string(value) {} //////////////////////////////////////////////////////////////////////////////// -Variant::Variant (const time_t value, const enum type new_type) -: _type (new_type) -{ - switch (new_type) - { - case type_date: _date = value; break; - case type_duration: _duration = value; break; - default: - throw std::string (STRING_VARIANT_TIME_T); +Variant::Variant(const time_t value, const enum type new_type) : _type(new_type) { + switch (new_type) { + case type_date: + _date = value; + break; + case type_duration: + _duration = value; + break; + default: + throw std::string(STRING_VARIANT_TIME_T); } } //////////////////////////////////////////////////////////////////////////////// -void Variant::source (const std::string& input) -{ - _source = input; -} +void Variant::source(const std::string& input) { _source = input; } //////////////////////////////////////////////////////////////////////////////// -const std::string& Variant::source () const -{ - return _source; -} +const std::string& Variant::source() const { return _source; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator= (const Variant& other) = default; +Variant& Variant::operator=(const Variant& other) = default; //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator&& (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator&&(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - left.cast (type_boolean); - right.cast (type_boolean); + left.cast(type_boolean); + right.cast(type_boolean); return left._bool && right._bool; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator|| (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator||(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - left.cast (type_boolean); - right.cast (type_boolean); + left.cast(type_boolean); + right.cast(type_boolean); return left._bool || right._bool; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator_xor (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator_xor(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - left.cast (type_boolean); - right.cast (type_boolean); + left.cast(type_boolean); + right.cast(type_boolean); - return (left._bool && !right._bool) || - (!left._bool && right._bool); + return (left._bool && !right._bool) || (!left._bool && right._bool); } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator< (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator<(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return !left._bool && right._bool; - case type_integer: left.cast (type_integer); return left._integer < right._integer; - case type_real: left.cast (type_real); return left._real < right._real; - case type_string: left.cast (type_string); return left._string < right._string; - case type_date: left.cast (type_date); return left._date < right._date; - case type_duration: left.cast (type_duration); return left._duration < right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer < right._integer; - case type_integer: return left._integer < right._integer; - case type_real: left.cast (type_real); return left._real < right._real; - case type_string: left.cast (type_string); return left._string < right._string; - case type_date: left.cast (type_date); return left._date < right._date; - case type_duration: left.cast (type_duration); return left._duration < right._duration; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: right.cast (type_real); return left._real < right._real; - case type_integer: right.cast (type_real); return left._real < right._real; - case type_real: return left._real < right._real; - case type_string: left.cast (type_string); return left._string < right._string; - case type_date: left.cast (type_date); return left._date < right._date; - case type_duration: left.cast (type_duration); return left._duration < right._duration; - } - break; - - case type_string: - switch (right._type) - { + switch (left._type) { case type_boolean: - case type_integer: - case type_real: - right.cast (type_string); - return left._string < right._string; - - case type_string: - { - if (left._string == right._string) - return false; - - auto order = Task::customOrder.find (left.source ()); - if (order != Task::customOrder.end ()) - { - // Guaranteed to be found, because of ColUDA::validate (). - auto posLeft = std::find (order->second.begin (), order->second.end (), left._string); - auto posRight = std::find (order->second.begin (), order->second.end (), right._string); - return posLeft < posRight; - } - else - { - if (left.trivial () || right.trivial ()) - return false; - + switch (right._type) { + case type_boolean: + return !left._bool && right._bool; + case type_integer: + left.cast(type_integer); + return left._integer < right._integer; + case type_real: + left.cast(type_real); + return left._real < right._real; + case type_string: + left.cast(type_string); return left._string < right._string; - } + case type_date: + left.cast(type_date); + return left._date < right._date; + case type_duration: + left.cast(type_duration); + return left._duration < right._duration; } + break; - case type_date: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_date); - return left._date < right._date; - - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_duration); - return left._duration < right._duration; - } - break; - - case type_date: - switch (right._type) - { - case type_boolean: case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer < right._integer; + case type_integer: + return left._integer < right._integer; + case type_real: + left.cast(type_real); + return left._real < right._real; + case type_string: + left.cast(type_string); + return left._string < right._string; + case type_date: + left.cast(type_date); + return left._date < right._date; + case type_duration: + left.cast(type_duration); + return left._duration < right._duration; + } + break; + case type_real: + switch (right._type) { + case type_boolean: + right.cast(type_real); + return left._real < right._real; + case type_integer: + right.cast(type_real); + return left._real < right._real; + case type_real: + return left._real < right._real; + case type_string: + left.cast(type_string); + return left._string < right._string; + case type_date: + left.cast(type_date); + return left._date < right._date; + case type_duration: + left.cast(type_duration); + return left._duration < right._duration; + } + break; + case type_string: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_string); + return left._string < right._string; + + case type_string: { + if (left._string == right._string) return false; + + auto order = Task::customOrder.find(left.source()); + if (order != Task::customOrder.end()) { + // Guaranteed to be found, because of ColUDA::validate (). + auto posLeft = std::find(order->second.begin(), order->second.end(), left._string); + auto posRight = std::find(order->second.begin(), order->second.end(), right._string); + return posLeft < posRight; + } else { + if (left.trivial() || right.trivial()) return false; + + return left._string < right._string; + } + } + + case type_date: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + return left._date < right._date; + + case type_duration: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_duration); + return left._duration < right._duration; + } + break; + case type_date: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; + + right.cast(type_date); + return left._date < right._date; + } + break; + case type_duration: - if (left.trivial () || right.trivial ()) - return false; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; - right.cast (type_date); - return left._date < right._date; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_duration); - return left._duration < right._duration; - } - break; + right.cast(type_duration); + return left._duration < right._duration; + } + break; } return false; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator<= (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator<=(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return !left._bool || right._bool; - case type_integer: left.cast (type_integer); return left._integer <= right._integer; - case type_real: left.cast (type_real); return left._real <= right._real; - case type_string: left.cast (type_string); return left._string <= right._string; - case type_date: left.cast (type_date); return left._date <= right._date; - case type_duration: left.cast (type_duration); return left._duration <= right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer <= right._integer; - case type_integer: return left._integer <= right._integer; - case type_real: left.cast (type_real); return left._real <= right._real; - case type_string: left.cast (type_string); return left._string <= right._string; - case type_date: left.cast (type_date); return left._date <= right._date; - case type_duration: left.cast (type_duration); return left._duration <= right._duration; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: right.cast (type_real); return left._real <= right._real; - case type_integer: right.cast (type_real); return left._real <= right._real; - case type_real: return left._real <= right._real; - case type_string: left.cast (type_string); return left._string <= right._string; - case type_date: left.cast (type_date); return left._date <= right._date; - case type_duration: left.cast (type_duration); return left._duration <= right._duration; - } - break; - - case type_string: - switch (right._type) - { + switch (left._type) { case type_boolean: - case type_integer: - case type_real: - right.cast (type_string); - return left._string <= right._string; - - case type_string: - { - if (left._string == right._string) - return true; - - auto order = Task::customOrder.find (left.source ()); - if (order != Task::customOrder.end ()) - { - // Guaranteed to be found, because of ColUDA::validate (). - auto posLeft = std::find (order->second.begin (), order->second.end (), left._string); - auto posRight = std::find (order->second.begin (), order->second.end (), right._string); - return posLeft <= posRight; - } - else - { - if (left.trivial () || right.trivial ()) - return false; - + switch (right._type) { + case type_boolean: + return !left._bool || right._bool; + case type_integer: + left.cast(type_integer); + return left._integer <= right._integer; + case type_real: + left.cast(type_real); + return left._real <= right._real; + case type_string: + left.cast(type_string); return left._string <= right._string; - } + case type_date: + left.cast(type_date); + return left._date <= right._date; + case type_duration: + left.cast(type_duration); + return left._duration <= right._duration; } + break; - case type_date: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_date); - return left._date <= right._date; - - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_duration); - return left._duration <= right._duration; - } - break; - - case type_date: - switch (right._type) - { - case type_boolean: case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer <= right._integer; + case type_integer: + return left._integer <= right._integer; + case type_real: + left.cast(type_real); + return left._real <= right._real; + case type_string: + left.cast(type_string); + return left._string <= right._string; + case type_date: + left.cast(type_date); + return left._date <= right._date; + case type_duration: + left.cast(type_duration); + return left._duration <= right._duration; + } + break; + case type_real: + switch (right._type) { + case type_boolean: + right.cast(type_real); + return left._real <= right._real; + case type_integer: + right.cast(type_real); + return left._real <= right._real; + case type_real: + return left._real <= right._real; + case type_string: + left.cast(type_string); + return left._string <= right._string; + case type_date: + left.cast(type_date); + return left._date <= right._date; + case type_duration: + left.cast(type_duration); + return left._duration <= right._duration; + } + break; + case type_string: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_string); + return left._string <= right._string; + + case type_string: { + if (left._string == right._string) return true; + + auto order = Task::customOrder.find(left.source()); + if (order != Task::customOrder.end()) { + // Guaranteed to be found, because of ColUDA::validate (). + auto posLeft = std::find(order->second.begin(), order->second.end(), left._string); + auto posRight = std::find(order->second.begin(), order->second.end(), right._string); + return posLeft <= posRight; + } else { + if (left.trivial() || right.trivial()) return false; + + return left._string <= right._string; + } + } + + case type_date: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + return left._date <= right._date; + + case type_duration: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_duration); + return left._duration <= right._duration; + } + break; + case type_date: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; + + right.cast(type_date); + return left._date <= right._date; + } + break; + case type_duration: - if (left.trivial () || right.trivial ()) - return false; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; - right.cast (type_date); - return left._date <= right._date; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_duration); - return left._duration <= right._duration; - } - break; + right.cast(type_duration); + return left._duration <= right._duration; + } + break; } return false; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator> (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator>(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return !left._bool && right._bool; - case type_integer: left.cast (type_integer); return left._integer > right._integer; - case type_real: left.cast (type_real); return left._real > right._real; - case type_string: left.cast (type_string); return left._string > right._string; - case type_date: left.cast (type_date); return left._date > right._date; - case type_duration: left.cast (type_duration); return left._duration > right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer > right._integer; - case type_integer: return left._integer > right._integer; - case type_real: left.cast (type_real); return left._real > right._real; - case type_string: left.cast (type_string); return left._string > right._string; - case type_date: left.cast (type_date); return left._date > right._date; - case type_duration: left.cast (type_duration); return left._duration > right._duration; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: right.cast (type_real); return left._real > right._real; - case type_integer: right.cast (type_real); return left._real > right._real; - case type_real: return left._real > right._real; - case type_string: left.cast (type_string); return left._string > right._string; - case type_date: left.cast (type_date); return left._date > right._date; - case type_duration: left.cast (type_duration); return left._duration > right._duration; - } - break; - - case type_string: - switch (right._type) - { + switch (left._type) { case type_boolean: - case type_integer: - case type_real: - right.cast (type_string); - return left._string > right._string; - - case type_string: - { - if (left._string == right._string) - return false; - - auto order = Task::customOrder.find (left.source ()); - if (order != Task::customOrder.end ()) - { - // Guaranteed to be found, because of ColUDA::validate (). - auto posLeft = std::find (order->second.begin (), order->second.end (), left._string); - auto posRight = std::find (order->second.begin (), order->second.end (), right._string); - return posLeft > posRight; - } - else - { - if (left.trivial () || right.trivial ()) - return false; - + switch (right._type) { + case type_boolean: + return !left._bool && right._bool; + case type_integer: + left.cast(type_integer); + return left._integer > right._integer; + case type_real: + left.cast(type_real); + return left._real > right._real; + case type_string: + left.cast(type_string); return left._string > right._string; - } + case type_date: + left.cast(type_date); + return left._date > right._date; + case type_duration: + left.cast(type_duration); + return left._duration > right._duration; } + break; - case type_date: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_date); - return left._date > right._date; - - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_duration); - return left._duration > right._duration; - } - break; - - case type_date: - switch (right._type) - { - case type_boolean: case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer > right._integer; + case type_integer: + return left._integer > right._integer; + case type_real: + left.cast(type_real); + return left._real > right._real; + case type_string: + left.cast(type_string); + return left._string > right._string; + case type_date: + left.cast(type_date); + return left._date > right._date; + case type_duration: + left.cast(type_duration); + return left._duration > right._duration; + } + break; + case type_real: + switch (right._type) { + case type_boolean: + right.cast(type_real); + return left._real > right._real; + case type_integer: + right.cast(type_real); + return left._real > right._real; + case type_real: + return left._real > right._real; + case type_string: + left.cast(type_string); + return left._string > right._string; + case type_date: + left.cast(type_date); + return left._date > right._date; + case type_duration: + left.cast(type_duration); + return left._duration > right._duration; + } + break; + case type_string: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_string); + return left._string > right._string; + + case type_string: { + if (left._string == right._string) return false; + + auto order = Task::customOrder.find(left.source()); + if (order != Task::customOrder.end()) { + // Guaranteed to be found, because of ColUDA::validate (). + auto posLeft = std::find(order->second.begin(), order->second.end(), left._string); + auto posRight = std::find(order->second.begin(), order->second.end(), right._string); + return posLeft > posRight; + } else { + if (left.trivial() || right.trivial()) return false; + + return left._string > right._string; + } + } + + case type_date: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + return left._date > right._date; + + case type_duration: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_duration); + return left._duration > right._duration; + } + break; + case type_date: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; + + right.cast(type_date); + return left._date > right._date; + } + break; + case type_duration: - if (left.trivial () || right.trivial ()) - return false; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; - right.cast (type_date); - return left._date > right._date; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_duration); - return left._duration > right._duration; - } - break; + right.cast(type_duration); + return left._duration > right._duration; + } + break; } return false; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator>= (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator>=(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return left._bool || !right._bool; - case type_integer: left.cast (type_integer); return left._integer >= right._integer; - case type_real: left.cast (type_real); return left._real >= right._real; - case type_string: left.cast (type_string); return left._string >= right._string; - case type_date: left.cast (type_date); return left._date >= right._date; - case type_duration: left.cast (type_duration); return left._duration >= right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer >= right._integer; - case type_integer: return left._integer >= right._integer; - case type_real: left.cast (type_real); return left._real >= right._real; - case type_string: left.cast (type_string); return left._string >= right._string; - case type_date: left.cast (type_date); return left._date >= right._date; - case type_duration: left.cast (type_duration); return left._duration >= right._duration; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: right.cast (type_real); return left._real >= right._real; - case type_integer: right.cast (type_real); return left._real >= right._real; - case type_real: return left._real >= right._real; - case type_string: left.cast (type_string); return left._string >= right._string; - case type_date: left.cast (type_date); return left._date >= right._date; - case type_duration: left.cast (type_duration); return left._duration >= right._duration; - } - break; - - case type_string: - switch (right._type) - { + switch (left._type) { case type_boolean: - case type_integer: - case type_real: - right.cast (type_string); - return left._string >= right._string; - - case type_string: - { - if (left._string == right._string) - return true; - - auto order = Task::customOrder.find (left.source ()); - if (order != Task::customOrder.end ()) - { - // Guaranteed to be found, because of ColUDA::validate (). - auto posLeft = std::find (order->second.begin (), order->second.end (), left._string); - auto posRight = std::find (order->second.begin (), order->second.end (), right._string); - return posLeft >= posRight; - } - else - { - if (left.trivial () || right.trivial ()) - return false; - + switch (right._type) { + case type_boolean: + return left._bool || !right._bool; + case type_integer: + left.cast(type_integer); + return left._integer >= right._integer; + case type_real: + left.cast(type_real); + return left._real >= right._real; + case type_string: + left.cast(type_string); return left._string >= right._string; - } + case type_date: + left.cast(type_date); + return left._date >= right._date; + case type_duration: + left.cast(type_duration); + return left._duration >= right._duration; } + break; - case type_date: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_date); - return left._date >= right._date; - - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_duration); - return left._duration >= right._duration; - } - break; - - case type_date: - switch (right._type) - { - case type_boolean: case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer >= right._integer; + case type_integer: + return left._integer >= right._integer; + case type_real: + left.cast(type_real); + return left._real >= right._real; + case type_string: + left.cast(type_string); + return left._string >= right._string; + case type_date: + left.cast(type_date); + return left._date >= right._date; + case type_duration: + left.cast(type_duration); + return left._duration >= right._duration; + } + break; + case type_real: + switch (right._type) { + case type_boolean: + right.cast(type_real); + return left._real >= right._real; + case type_integer: + right.cast(type_real); + return left._real >= right._real; + case type_real: + return left._real >= right._real; + case type_string: + left.cast(type_string); + return left._string >= right._string; + case type_date: + left.cast(type_date); + return left._date >= right._date; + case type_duration: + left.cast(type_duration); + return left._duration >= right._duration; + } + break; + case type_string: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_string); + return left._string >= right._string; + + case type_string: { + if (left._string == right._string) return true; + + auto order = Task::customOrder.find(left.source()); + if (order != Task::customOrder.end()) { + // Guaranteed to be found, because of ColUDA::validate (). + auto posLeft = std::find(order->second.begin(), order->second.end(), left._string); + auto posRight = std::find(order->second.begin(), order->second.end(), right._string); + return posLeft >= posRight; + } else { + if (left.trivial() || right.trivial()) return false; + + return left._string >= right._string; + } + } + + case type_date: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + return left._date >= right._date; + + case type_duration: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_duration); + return left._duration >= right._duration; + } + break; + case type_date: + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; + + right.cast(type_date); + return left._date >= right._date; + } + break; + case type_duration: - if (left.trivial () || right.trivial ()) - return false; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; - right.cast (type_date); - return left._date >= right._date; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_duration); - return left._duration >= right._duration; - } - break; + right.cast(type_duration); + return left._duration >= right._duration; + } + break; } return false; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator== (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator==(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return left._bool == right._bool; - case type_integer: left.cast (type_integer); return left._integer == right._integer; - case type_real: left.cast (type_real); return left._real == right._real; - case type_string: left.cast (type_string); return left._string == right._string; - case type_date: left.cast (type_date); return left._date == right._date; - case type_duration: left.cast (type_duration); return left._duration == right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer == right._integer; - case type_integer: return left._integer == right._integer; - case type_real: left.cast (type_real); return left._real == right._real; - case type_string: left.cast (type_string); return left._string == right._string; - case type_date: left.cast (type_date); return left._date == right._date; - case type_duration: left.cast (type_duration); return left._duration == right._duration; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: right.cast (type_real); return left._real == right._real; - case type_integer: right.cast (type_real); return left._real == right._real; - case type_real: return left._real == right._real; - case type_string: left.cast (type_string); return left._string == right._string; - case type_date: left.cast (type_date); return left._date == right._date; - case type_duration: left.cast (type_duration); return left._duration == right._duration; - } - break; - - case type_string: - switch (right._type) - { + switch (left._type) { case type_boolean: + switch (right._type) { + case type_boolean: + return left._bool == right._bool; + case type_integer: + left.cast(type_integer); + return left._integer == right._integer; + case type_real: + left.cast(type_real); + return left._real == right._real; + case type_string: + left.cast(type_string); + return left._string == right._string; + case type_date: + left.cast(type_date); + return left._date == right._date; + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; + case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer == right._integer; + case type_integer: + return left._integer == right._integer; + case type_real: + left.cast(type_real); + return left._real == right._real; + case type_string: + left.cast(type_string); + return left._string == right._string; + case type_date: + left.cast(type_date); + return left._date == right._date; + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; + case type_real: + switch (right._type) { + case type_boolean: + right.cast(type_real); + return left._real == right._real; + case type_integer: + right.cast(type_real); + return left._real == right._real; + case type_real: + return left._real == right._real; + case type_string: + left.cast(type_string); + return left._string == right._string; + case type_date: + left.cast(type_date); + return left._date == right._date; + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; + case type_string: - right.cast (type_string); + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + right.cast(type_string); - // Status is always compared caseless. - if (left.source () == "status") - return compare (left._string, right._string, false); + // Status is always compared caseless. + if (left.source() == "status") return compare(left._string, right._string, false); - return left._string == right._string; + return left._string == right._string; + + case type_date: + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + return left._date == right._date; + + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; case type_date: - if (left.trivial () || right.trivial ()) - return false; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + if (left.trivial() || right.trivial()) return false; - left.cast (type_date); - return left._date == right._date; + right.cast(type_date); + return left._date == right._date; + } + break; case type_duration: - left.cast (type_duration); - return left._duration == right._duration; - } - break; - - case type_date: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_date); - return left._date == right._date; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - right.cast (type_duration); - return left._duration == right._duration; - } - break; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + right.cast(type_duration); + return left._duration == right._duration; + } + break; } return false; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator!= (const Variant& other) const -{ - return !(*this == other); -} +bool Variant::operator!=(const Variant& other) const { return !(*this == other); } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator_match (const Variant& other, const Task& task) const -{ +bool Variant::operator_match(const Variant& other, const Task& task) const { // Simple matching case first. - Variant left (*this); - Variant right (other); + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - left.cast (type_string); - right.cast (type_string); + left.cast(type_string); + right.cast(type_string); std::string pattern = right._string; - Lexer::dequote (pattern); + Lexer::dequote(pattern); - if (searchUsingRegex) - { - RX r (pattern, searchCaseSensitive); - if (r.match (left._string)) - return true; + if (searchUsingRegex) { + RX r(pattern, searchCaseSensitive); + if (r.match(left._string)) return true; // If the above did not match, and the left source is "description", then // in the annotations. - if (left.source () == "description") - { - for (auto& a : task.getAnnotations ()) - if (r.match (a.second)) - return true; + if (left.source() == "description") { + for (auto& a : task.getAnnotations()) + if (r.match(a.second)) return true; } - } - else - { + } else { // If pattern starts with '^', look for a leftmost compare only. - if (pattern[0] == '^' && - find (left._string, - pattern.substr (1), - searchCaseSensitive) == 0) - { + if (pattern[0] == '^' && find(left._string, pattern.substr(1), searchCaseSensitive) == 0) { return true; } // If pattern ends with '$', look for a rightmost compare only. - else if (pattern[pattern.length () - 1] == '$' && - find (left._string, - pattern.substr (0, pattern.length () - 1), - searchCaseSensitive) == (left._string.length () - pattern.length () + 1)) - { + else if (pattern[pattern.length() - 1] == '$' && + find(left._string, pattern.substr(0, pattern.length() - 1), searchCaseSensitive) == + (left._string.length() - pattern.length() + 1)) { return true; } - else if (find (left._string, pattern, searchCaseSensitive) != std::string::npos) + else if (find(left._string, pattern, searchCaseSensitive) != std::string::npos) return true; // If the above did not match, and the left source is "description", then // in the annotations. - if (left.source () == "description") - { - for (auto& a : task.getAnnotations ()) - if (find (a.second, pattern, searchCaseSensitive) != std::string::npos) - return true; + if (left.source() == "description") { + for (auto& a : task.getAnnotations()) + if (find(a.second, pattern, searchCaseSensitive) != std::string::npos) return true; } } @@ -953,9 +971,8 @@ bool Variant::operator_match (const Variant& other, const Task& task) const } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator_nomatch (const Variant& other, const Task& task) const -{ - return ! operator_match (other, task); +bool Variant::operator_nomatch(const Variant& other, const Task& task) const { + return !operator_match(other, task); } //////////////////////////////////////////////////////////////////////////////// @@ -964,178 +981,173 @@ bool Variant::operator_nomatch (const Variant& other, const Task& task) const // date date --> same day check // string string --> leftmost // -bool Variant::operator_partial (const Variant& other) const -{ - Variant left (*this); - Variant right (other); +bool Variant::operator_partial(const Variant& other) const { + Variant left(*this); + Variant right(other); - if (left._type == type_string) - Lexer::dequote (left._string); + if (left._type == type_string) Lexer::dequote(left._string); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (left._type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: return left._bool == right._bool; - case type_integer: left.cast (type_integer); return left._integer == right._integer; - case type_real: left.cast (type_real); return left._real == right._real; - case type_string: left.cast (type_string); return left._string == right._string; - - // Same-day comparison. - case type_date: - { - left.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); - } - - case type_duration: left.cast (type_duration); return left._duration == right._duration; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); return left._integer == right._integer; - case type_integer: return left._integer == right._integer; - case type_real: left.cast (type_real); return left._real == right._real; - case type_string: left.cast (type_string); return left._string == right._string; - - // Same-day comparison. - case type_date: - { - left.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); - } - - case type_duration: left.cast (type_duration); return left._duration == right._duration; - } - break; - - case type_real: - switch (right._type) - { + switch (left._type) { case type_boolean: + switch (right._type) { + case type_boolean: + return left._bool == right._bool; + case type_integer: + left.cast(type_integer); + return left._integer == right._integer; + case type_real: + left.cast(type_real); + return left._real == right._real; + case type_string: + left.cast(type_string); + return left._string == right._string; + + // Same-day comparison. + case type_date: { + left.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; + case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + return left._integer == right._integer; + case type_integer: + return left._integer == right._integer; + case type_real: + left.cast(type_real); + return left._real == right._real; + case type_string: + left.cast(type_string); + return left._string == right._string; + + // Same-day comparison. + case type_date: { + left.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; + case type_real: - right.cast (type_real); - return left._real == right._real; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_real); + return left._real == right._real; + + case type_string: + left.cast(type_string); + return left._string == right._string; + + // Same-day comparison. + case type_date: { + left.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; + } + break; case type_string: - left.cast (type_string); - return left._string == right._string; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + case type_string: { + right.cast(type_string); - // Same-day comparison. - case type_date: - { - left.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); + // Status is always compared caseless. + if (left.source() == "status") return compare(left._string, right._string, false); + + int left_len = left._string.length(); + int right_len = right._string.length(); + + if ((left_len == 0 && right_len != 0) || (left_len != 0 && right_len == 0)) return false; + + // Dodgy. + if (left._string.length() < right._string.length()) return false; + + return left._string.substr(0, right._string.length()) == right._string; + } + + // Same-day comparison. + case type_date: { + if (left.trivial() || right.trivial()) return false; + + left.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + + case type_duration: + left.cast(type_duration); + return left._duration == right._duration; } + break; + + case type_date: + switch (right._type) { + // Same-day comparison. + case type_string: { + if (left.trivial() || right.trivial()) return false; + + right.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + + case type_boolean: + case type_integer: + case type_real: + case type_date: + case type_duration: { + right.cast(type_date); + Datetime left_date(left._date); + Datetime right_date(right._date); + return left_date.sameDay(right_date); + } + } + break; case type_duration: - left.cast (type_duration); - return left._duration == right._duration; - } - break; - - case type_string: - switch (right._type) - { - case type_boolean: - case type_integer: - case type_real: - case type_string: - { - right.cast (type_string); - - // Status is always compared caseless. - if (left.source () == "status") - return compare (left._string, right._string, false); - - int left_len = left._string.length (); - int right_len = right._string.length (); - - if ((left_len == 0 && right_len != 0) || - (left_len != 0 && right_len == 0)) - return false; - - // Dodgy. - if (left._string.length () < right._string.length ()) - return false; - - return left._string.substr (0, right._string.length ()) == right._string; + switch (right._type) { + // Same-day comparison. + case type_boolean: + case type_integer: + case type_real: + case type_string: + case type_date: + case type_duration: + right.cast(type_duration); + return left._duration == right._duration; } - - // Same-day comparison. - case type_date: - { - if (left.trivial () || right.trivial ()) - return false; - - left.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); - } - - case type_duration: - left.cast (type_duration); - return left._duration == right._duration; - } - break; - - case type_date: - switch (right._type) - { - // Same-day comparison. - case type_string: - { - if (left.trivial () || right.trivial ()) - return false; - - right.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); - } - - case type_boolean: - case type_integer: - case type_real: - case type_date: - case type_duration: - { - right.cast (type_date); - Datetime left_date (left._date); - Datetime right_date (right._date); - return left_date.sameDay (right_date); - } - } - break; - - case type_duration: - switch (right._type) - { - // Same-day comparison. - case type_boolean: - case type_integer: - case type_real: - case type_string: - case type_date: - case type_duration: - right.cast (type_duration); - return left._duration == right._duration; - } - break; + break; } return false; @@ -1143,860 +1155,949 @@ bool Variant::operator_partial (const Variant& other) const //////////////////////////////////////////////////////////////////////////////// // Inverse of operator_partial. -bool Variant::operator_nopartial (const Variant& other) const -{ - return ! operator_partial (other); +bool Variant::operator_nopartial(const Variant& other) const { return !operator_partial(other); } + +//////////////////////////////////////////////////////////////////////////////// +bool Variant::operator_hastag(const Variant& other, const Task& task) const { + Variant right(other); + right.cast(type_string); + Lexer::dequote(right._string); + return task.hasTag(right._string); } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator_hastag (const Variant& other, const Task& task) const -{ - Variant right (other); - right.cast (type_string); - Lexer::dequote (right._string); - return task.hasTag (right._string); +bool Variant::operator_notag(const Variant& other, const Task& task) const { + return !operator_hastag(other, task); } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator_notag (const Variant& other, const Task& task) const -{ - return ! operator_hastag (other, task); +bool Variant::operator!() const { + Variant left(*this); + + if (left._type == type_string) Lexer::dequote(left._string); + + left.cast(type_boolean); + return !left._bool; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::operator! () const -{ - Variant left (*this); +Variant& Variant::operator^=(const Variant& other) { + switch (_type) { + case type_boolean: + throw std::string(STRING_VARIANT_EXP_BOOL); + break; - if (left._type == type_string) - Lexer::dequote (left._string); + case type_integer: + switch (other._type) { + case type_boolean: + throw std::string(STRING_VARIANT_EXP_BOOL); + case type_integer: + _integer = (int)pow(static_cast(_integer), static_cast(other._integer)); + break; + case type_real: + throw std::string(STRING_VARIANT_EXP_NON_INT); + case type_string: + throw std::string(STRING_VARIANT_EXP_STRING); + case type_date: + throw std::string(STRING_VARIANT_EXP_DATE); + case type_duration: + throw std::string(STRING_VARIANT_EXP_DURATION); + } + break; - left.cast (type_boolean); - return ! left._bool; -} + case type_real: + switch (other._type) { + case type_boolean: + throw std::string(STRING_VARIANT_EXP_BOOL); + case type_integer: + _real = pow(_real, static_cast(other._integer)); + break; + case type_real: + throw std::string(STRING_VARIANT_EXP_NON_INT); + case type_string: + throw std::string(STRING_VARIANT_EXP_STRING); + case type_date: + throw std::string(STRING_VARIANT_EXP_DATE); + case type_duration: + throw std::string(STRING_VARIANT_EXP_DURATION); + } + break; -//////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator^= (const Variant& other) -{ - switch (_type) - { - case type_boolean: - throw std::string (STRING_VARIANT_EXP_BOOL); - break; + case type_string: + throw std::string(STRING_VARIANT_EXP_STRING); + break; - case type_integer: - switch (other._type) - { - case type_boolean: throw std::string (STRING_VARIANT_EXP_BOOL); - case type_integer: _integer = (int) pow (static_cast(_integer), static_cast(other._integer)); break; - case type_real: throw std::string (STRING_VARIANT_EXP_NON_INT); - case type_string: throw std::string (STRING_VARIANT_EXP_STRING); - case type_date: throw std::string (STRING_VARIANT_EXP_DATE); - case type_duration: throw std::string (STRING_VARIANT_EXP_DURATION); - } - break; + case type_date: + throw std::string(STRING_VARIANT_EXP_DATE); + break; - case type_real: - switch (other._type) - { - case type_boolean: throw std::string (STRING_VARIANT_EXP_BOOL); - case type_integer: _real = pow (_real, static_cast(other._integer)); break; - case type_real: throw std::string (STRING_VARIANT_EXP_NON_INT); - case type_string: throw std::string (STRING_VARIANT_EXP_STRING); - case type_date: throw std::string (STRING_VARIANT_EXP_DATE); - case type_duration: throw std::string (STRING_VARIANT_EXP_DURATION); - } - break; - - case type_string: - throw std::string (STRING_VARIANT_EXP_STRING); - break; - - case type_date: - throw std::string (STRING_VARIANT_EXP_DATE); - break; - - case type_duration: - throw std::string (STRING_VARIANT_EXP_DURATION); - break; + case type_duration: + throw std::string(STRING_VARIANT_EXP_DURATION); + break; } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator^ (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator^(const Variant& other) const { + Variant left(*this); left ^= other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator-= (const Variant& other) -{ - Variant right (other); - - switch (_type) - { - case type_boolean: - throw std::string (STRING_VARIANT_SUB_BOOL); - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); _integer -= right._integer; break; - case type_integer: _integer -= right._integer; break; - case type_real: cast (type_real); _real -= right._real; break; - case type_string: throw std::string (STRING_VARIANT_SUB_STRING); - case type_date: cast (type_date); _date -= right._date; break; - case type_duration: cast (type_duration); _duration -= right._duration; break; - } - break; - - case type_real: - switch (right._type) - { - case type_string: - throw std::string (STRING_VARIANT_SUB_STRING); +Variant& Variant::operator-=(const Variant& other) { + Variant right(other); + switch (_type) { case type_boolean: - case type_integer: - case type_real: - case type_date: - case type_duration: - right.cast (type_real); - _real -= right._real; + throw std::string(STRING_VARIANT_SUB_BOOL); + break; + + case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + _integer -= right._integer; + break; + case type_integer: + _integer -= right._integer; + break; + case type_real: + cast(type_real); + _real -= right._real; + break; + case type_string: + throw std::string(STRING_VARIANT_SUB_STRING); + case type_date: + cast(type_date); + _date -= right._date; + break; + case type_duration: + cast(type_duration); + _duration -= right._duration; + break; + } + break; + + case type_real: + switch (right._type) { + case type_string: + throw std::string(STRING_VARIANT_SUB_STRING); + + case type_boolean: + case type_integer: + case type_real: + case type_date: + case type_duration: + right.cast(type_real); + _real -= right._real; + break; + } break; - } - break; - case type_string: - switch (right._type) - { case type_string: - cast (type_string); _string += '-' + right._string; break; - case type_boolean: - case type_integer: - case type_real: - case type_date: - case type_duration: - throw std::string (STRING_VARIANT_SUB_STRING); + switch (right._type) { + case type_string: + cast(type_string); + _string += '-' + right._string; + break; + case type_boolean: + case type_integer: + case type_real: + case type_date: + case type_duration: + throw std::string(STRING_VARIANT_SUB_STRING); + break; + } break; - } - break; - case type_date: - switch (right._type) - { - case type_boolean: right.cast (type_integer); _date -= right._integer; break; - case type_integer: _date -= right._integer; break; - case type_real: _date -= (int) right._real; break; - case type_string: throw std::string (STRING_VARIANT_SUB_STRING); - case type_date: _type = Variant::type_duration; _duration = _date - right._date; break; - case type_duration: _date -= right._duration; break; - } - break; + case type_date: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + _date -= right._integer; + break; + case type_integer: + _date -= right._integer; + break; + case type_real: + _date -= (int)right._real; + break; + case type_string: + throw std::string(STRING_VARIANT_SUB_STRING); + case type_date: + _type = Variant::type_duration; + _duration = _date - right._date; + break; + case type_duration: + _date -= right._duration; + break; + } + break; - case type_duration: - switch (right._type) - { - case type_boolean: right.cast (type_integer); _duration -= right._integer; break; - case type_integer: _duration -= right._integer; break; - case type_real: _duration -= (int) right._real; break; - case type_string: throw std::string (STRING_VARIANT_SUB_STRING); - case type_date: throw std::string (STRING_VARIANT_SUB_DATE); - case type_duration: _duration -= right._duration; break; - } - break; + case type_duration: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + _duration -= right._integer; + break; + case type_integer: + _duration -= right._integer; + break; + case type_real: + _duration -= (int)right._real; + break; + case type_string: + throw std::string(STRING_VARIANT_SUB_STRING); + case type_date: + throw std::string(STRING_VARIANT_SUB_DATE); + case type_duration: + _duration -= right._duration; + break; + } + break; } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator- (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator-(const Variant& other) const { + Variant left(*this); left -= other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator+= (const Variant& other) -{ - Variant right (other); +Variant& Variant::operator+=(const Variant& other) { + Variant right(other); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (_type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: throw std::string (STRING_VARIANT_ADD_BOOL); - case type_integer: cast (type_integer); _integer += right._integer; break; - case type_real: cast (type_real); _real += right._real; break; - case type_string: cast (type_string); _string += right._string; break; - case type_date: cast (type_date); _date += right._date; break; - case type_duration: cast (type_duration); _duration += right._duration; break; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); _integer += right._integer; break; - case type_integer: _integer += right._integer; break; - case type_real: cast (type_real); _real += right._real; break; - case type_string: cast (type_string); _string += right._string; break; - case type_date: cast (type_date); _date += right._date; break; - case type_duration: cast (type_duration); _duration += right._duration; break; - } - break; - - case type_real: - switch (right._type) - { + switch (_type) { case type_boolean: + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_ADD_BOOL); + case type_integer: + cast(type_integer); + _integer += right._integer; + break; + case type_real: + cast(type_real); + _real += right._real; + break; + case type_string: + cast(type_string); + _string += right._string; + break; + case type_date: + cast(type_date); + _date += right._date; + break; + case type_duration: + cast(type_duration); + _duration += right._duration; + break; + } + break; + case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + _integer += right._integer; + break; + case type_integer: + _integer += right._integer; + break; + case type_real: + cast(type_real); + _real += right._real; + break; + case type_string: + cast(type_string); + _string += right._string; + break; + case type_date: + cast(type_date); + _date += right._date; + break; + case type_duration: + cast(type_duration); + _duration += right._duration; + break; + } + break; + case type_real: - right.cast (type_real); - _real += right._real; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_real); + _real += right._real; + break; + + case type_string: + cast(type_string); + _string += right._string; + break; + + case type_date: + _type = type_date; + _date = (unsigned)(int)_real + right._date; + break; + + case type_duration: + _type = type_duration; + _duration = (unsigned)(int)_real + right._duration; + break; + } break; case type_string: - cast (type_string); - _string += right._string; + _string += (std::string)right; break; case type_date: - _type = type_date; - _date = (unsigned) (int) _real + right._date; + switch (right._type) { + case type_boolean: + right.cast(type_date); + _date += right._date; + break; + case type_integer: + _date += right._integer; + break; + case type_real: + _date += (int)right._real; + break; + case type_string: + cast(type_string); + _string += right._string; + break; + case type_date: + throw std::string(STRING_VARIANT_ADD_DATE); + case type_duration: + _date += right._duration; + break; + } break; case type_duration: - _type = type_duration; - _duration = (unsigned) (int) _real + right._duration; + switch (right._type) { + case type_boolean: + right.cast(type_duration); + _duration += right._duration; + break; + case type_integer: + _duration += right._integer; + break; + case type_real: + _duration += (int)right._real; + break; + case type_string: + cast(type_string); + _string += right._string; + break; + case type_date: + _type = Variant::type_date; + _date += right._date + _duration; + break; + case type_duration: + _duration += right._duration; + break; + } break; - } - break; - - case type_string: - _string += (std::string) right; - break; - - case type_date: - switch (right._type) - { - case type_boolean: right.cast (type_date); _date += right._date; break; - case type_integer: _date += right._integer; break; - case type_real: _date += (int) right._real; break; - case type_string: cast (type_string); _string += right._string; break; - case type_date: throw std::string (STRING_VARIANT_ADD_DATE); - case type_duration: _date += right._duration; break; - } - break; - - case type_duration: - switch (right._type) - { - case type_boolean: right.cast (type_duration); _duration += right._duration; break; - case type_integer: _duration += right._integer; break; - case type_real: _duration += (int) right._real; break; - case type_string: cast (type_string); _string += right._string; break; - case type_date: _type = Variant::type_date; _date += right._date + _duration; break; - case type_duration: _duration += right._duration; break; - } - break; } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator+ (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator+(const Variant& other) const { + Variant left(*this); left += other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator*= (const Variant& other) -{ - Variant right (other); +Variant& Variant::operator*=(const Variant& other) { + Variant right(other); - if (right._type == type_string) - Lexer::dequote (right._string); + if (right._type == type_string) Lexer::dequote(right._string); - switch (_type) - { - case type_boolean: - switch (right._type) - { - case type_boolean: throw std::string (STRING_VARIANT_MUL_BOOL); - case type_integer: cast (type_integer); _integer *= right._integer; break; - case type_real: cast (type_real); _real *= right._real; break; - case type_string: _string = (_bool ? right._string : ""); _type = type_string; break; - case type_date: throw std::string (STRING_VARIANT_MUL_DATE); - case type_duration: cast (type_duration); _duration *= right._duration; break; - } - break; - - case type_integer: - switch (right._type) - { - case type_boolean: right.cast (type_integer); _integer *= right._integer; break; - case type_integer: _integer *= right._integer; break; - case type_real: cast (type_real); _real *= right._real; break; - case type_string: - { - int limit = _integer; - // assert (limit < 128); - _type = type_string; - _string = ""; - while (limit--) - _string += right._string; + switch (_type) { + case type_boolean: + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_MUL_BOOL); + case type_integer: + cast(type_integer); + _integer *= right._integer; + break; + case type_real: + cast(type_real); + _real *= right._real; + break; + case type_string: + _string = (_bool ? right._string : ""); + _type = type_string; + break; + case type_date: + throw std::string(STRING_VARIANT_MUL_DATE); + case type_duration: + cast(type_duration); + _duration *= right._duration; + break; } break; - case type_date: throw std::string (STRING_VARIANT_MUL_DATE); - case type_duration: cast (type_duration); _duration *= right._duration; break; - } - break; - case type_real: - switch (right._type) - { - case type_boolean: case type_integer: + switch (right._type) { + case type_boolean: + right.cast(type_integer); + _integer *= right._integer; + break; + case type_integer: + _integer *= right._integer; + break; + case type_real: + cast(type_real); + _real *= right._real; + break; + case type_string: { + int limit = _integer; + // assert (limit < 128); + _type = type_string; + _string = ""; + while (limit--) _string += right._string; + } break; + case type_date: + throw std::string(STRING_VARIANT_MUL_DATE); + case type_duration: + cast(type_duration); + _duration *= right._duration; + break; + } + break; + case type_real: - right.cast (type_real); - _real *= right._real; + switch (right._type) { + case type_boolean: + case type_integer: + case type_real: + right.cast(type_real); + _real *= right._real; + break; + + case type_string: + throw std::string(STRING_VARIANT_MUL_REAL_STR); + + case type_date: + throw std::string(STRING_VARIANT_MUL_DATE); + + case type_duration: + _type = type_duration; + _duration = (time_t)(unsigned)(int)(_real * static_cast(right._duration)); + } break; case type_string: - throw std::string (STRING_VARIANT_MUL_REAL_STR); + switch (right._type) { + case type_boolean: + if (!right._bool) _string = ""; + break; + case type_integer: { + int limit = right._integer - 1; + // assert (limit < 128); + std::string fragment = _string; + while (limit--) _string += fragment; + } break; + case type_real: + throw std::string(STRING_VARIANT_MUL_STR_REAL); + case type_string: + throw std::string(STRING_VARIANT_MUL_STR_STR); + case type_date: + throw std::string(STRING_VARIANT_MUL_STR_DATE); + case type_duration: + throw std::string(STRING_VARIANT_MUL_STR_DUR); + } + break; case type_date: - throw std::string (STRING_VARIANT_MUL_DATE); + throw std::string(STRING_VARIANT_MUL_DATE); case type_duration: - _type = type_duration; - _duration = (time_t) (unsigned) (int) (_real * static_cast(right._duration)); - } - break; - - case type_string: - switch (right._type) - { - case type_boolean: if (! right._bool) _string = ""; break; - case type_integer: - { - int limit = right._integer - 1; - // assert (limit < 128); - std::string fragment = _string; - while (limit--) - _string += fragment; + switch (right._type) { + case type_boolean: + right.cast(type_duration); + _duration *= right._duration; + break; + case type_integer: + _duration *= right._integer; + break; + case type_real: + _duration = (time_t)(unsigned)(int)(static_cast(_duration) * right._real); + break; + case type_string: + throw std::string(STRING_VARIANT_MUL_DUR_STR); + case type_date: + throw std::string(STRING_VARIANT_MUL_DUR_DATE); + case type_duration: + throw std::string(STRING_VARIANT_MUL_DUR_DUR); } break; - case type_real: throw std::string (STRING_VARIANT_MUL_STR_REAL); - case type_string: throw std::string (STRING_VARIANT_MUL_STR_STR); - case type_date: throw std::string (STRING_VARIANT_MUL_STR_DATE); - case type_duration: throw std::string (STRING_VARIANT_MUL_STR_DUR); - } - break; - - case type_date: - throw std::string (STRING_VARIANT_MUL_DATE); - - case type_duration: - switch (right._type) - { - case type_boolean: right.cast (type_duration); _duration *= right._duration; break; - case type_integer: _duration *= right._integer; break; - case type_real: - _duration = (time_t) (unsigned) (int) (static_cast(_duration) * right._real); - break; - case type_string: throw std::string (STRING_VARIANT_MUL_DUR_STR); - case type_date: throw std::string (STRING_VARIANT_MUL_DUR_DATE); - case type_duration: throw std::string (STRING_VARIANT_MUL_DUR_DUR); - } - break; } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator* (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator*(const Variant& other) const { + Variant left(*this); left *= other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator/= (const Variant& other) -{ - Variant right (other); +Variant& Variant::operator/=(const Variant& other) { + Variant right(other); - switch (_type) - { - case type_boolean: - throw std::string (STRING_VARIANT_DIV_BOOL); - break; - - case type_integer: - switch (right._type) - { + switch (_type) { case type_boolean: - throw std::string (STRING_VARIANT_DIV_INT_BOOL); + throw std::string(STRING_VARIANT_DIV_BOOL); + break; case type_integer: - if (right._integer == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _integer /= right._integer; + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_DIV_INT_BOOL); + + case type_integer: + if (right._integer == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _integer /= right._integer; + break; + + case type_real: + if (right._real == 0.0) throw std::string(STRING_VARIANT_DIV_ZERO); + cast(type_real); + _real /= right._real; + break; + + case type_string: + throw std::string(STRING_VARIANT_DIV_INT_STR); + + case type_date: + throw std::string(STRING_VARIANT_DIV_INT_DATE); + + case type_duration: + if (right._duration == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _type = type_duration; + _duration = (time_t)(unsigned)(int)(_integer / right._duration); + break; + } break; case type_real: - if (right._real == 0.0) - throw std::string (STRING_VARIANT_DIV_ZERO); - cast (type_real); - _real /= right._real; + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_DIV_REAL_BOOL); + + case type_integer: + if (right._integer == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _real /= static_cast(right._integer); + break; + + case type_real: + if (right._real == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _real /= right._real; + break; + + case type_string: + throw std::string(STRING_VARIANT_DIV_REAL_STR); + + case type_date: + throw std::string(STRING_VARIANT_DIV_REAL_DATE); + + case type_duration: + if (right._duration == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _type = type_duration; + _duration = (time_t)(unsigned)(int)(_real / right._duration); + break; + } break; case type_string: - throw std::string (STRING_VARIANT_DIV_INT_STR); + throw std::string(STRING_VARIANT_DIV_REAL_STR); + break; case type_date: - throw std::string (STRING_VARIANT_DIV_INT_DATE); + throw std::string(STRING_VARIANT_DIV_REAL_DATE); case type_duration: - if (right._duration == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _type = type_duration; - _duration = (time_t) (unsigned) (int) (_integer / right._duration); + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_DIV_DUR_BOOL); + + case type_integer: + if (right._integer == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _duration /= right._integer; + break; + + case type_real: + if (right._real == 0) throw std::string(STRING_VARIANT_DIV_ZERO); + _duration = (time_t)(unsigned)(int)(static_cast(_duration) / right._real); + break; + + case type_string: + throw std::string(STRING_VARIANT_DIV_DUR_STR); + + case type_date: + throw std::string(STRING_VARIANT_DIV_DUR_DATE); + + case type_duration: + _type = type_real; + _real = static_cast(_duration) / static_cast(right._duration); + break; + } break; - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: - throw std::string (STRING_VARIANT_DIV_REAL_BOOL); - - case type_integer: - if (right._integer == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _real /= static_cast(right._integer); - break; - - case type_real: - if (right._real == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _real /= right._real; - break; - - case type_string: - throw std::string (STRING_VARIANT_DIV_REAL_STR); - - case type_date: - throw std::string (STRING_VARIANT_DIV_REAL_DATE); - - case type_duration: - if (right._duration == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _type = type_duration; - _duration = (time_t) (unsigned) (int) (_real / right._duration); - break; - } - break; - - case type_string: - throw std::string (STRING_VARIANT_DIV_REAL_STR); - break; - - case type_date: - throw std::string (STRING_VARIANT_DIV_REAL_DATE); - - case type_duration: - switch (right._type) - { - case type_boolean: - throw std::string (STRING_VARIANT_DIV_DUR_BOOL); - - case type_integer: - if (right._integer == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _duration /= right._integer; - break; - - case type_real: - if (right._real == 0) - throw std::string (STRING_VARIANT_DIV_ZERO); - _duration = (time_t) (unsigned) (int) (static_cast(_duration) / right._real); - break; - - case type_string: - throw std::string (STRING_VARIANT_DIV_DUR_STR); - - case type_date: - throw std::string (STRING_VARIANT_DIV_DUR_DATE); - - case type_duration: - throw std::string (STRING_VARIANT_DIV_DUR_DUR); - } - break; } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator/ (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator/(const Variant& other) const { + Variant left(*this); left /= other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant& Variant::operator%= (const Variant& other) -{ - Variant right (other); +Variant& Variant::operator%=(const Variant& other) { + Variant right(other); - switch (_type) - { - case type_boolean: - throw std::string (STRING_VARIANT_MOD_BOOL); - break; - - case type_integer: - switch (right._type) - { + switch (_type) { case type_boolean: - throw std::string (STRING_VARIANT_MOD_INT_BOOL); + throw std::string(STRING_VARIANT_MOD_BOOL); + break; case type_integer: - if (right._integer == 0) - throw std::string (STRING_VARIANT_MOD_ZERO); - _integer %= right._integer; + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_MOD_INT_BOOL); + + case type_integer: + if (right._integer == 0) throw std::string(STRING_VARIANT_MOD_ZERO); + _integer %= right._integer; + break; + + case type_real: + if (right._real == 0.0) throw std::string(STRING_VARIANT_MOD_ZERO); + cast(type_real); + _real = fmod(_real, right._real); + break; + + case type_string: + throw std::string(STRING_VARIANT_MOD_INT_STR); + + case type_date: + throw std::string(STRING_VARIANT_MOD_INT_DATE); + + case type_duration: + throw std::string(STRING_VARIANT_MOD_INT_DUR); + } break; case type_real: - if (right._real == 0.0) - throw std::string (STRING_VARIANT_MOD_ZERO); - cast (type_real); - _real = fmod (_real, right._real); + switch (right._type) { + case type_boolean: + throw std::string(STRING_VARIANT_MOD_REAL_BOOL); + + case type_integer: + if (right._integer == 0) throw std::string(STRING_VARIANT_MOD_ZERO); + _real = fmod(_real, static_cast(right._integer)); + break; + + case type_real: + if (right._real == 0) throw std::string(STRING_VARIANT_MOD_ZERO); + _real = fmod(_real, right._real); + break; + + case type_string: + throw std::string(STRING_VARIANT_MOD_REAL_STR); + + case type_date: + throw std::string(STRING_VARIANT_MOD_REAL_DATE); + + case type_duration: + throw std::string(STRING_VARIANT_MOD_REAL_DUR); + } break; case type_string: - throw std::string (STRING_VARIANT_MOD_INT_STR); + throw std::string(STRING_VARIANT_MOD_STR); case type_date: - throw std::string (STRING_VARIANT_MOD_INT_DATE); + throw std::string(STRING_VARIANT_MOD_DATE); case type_duration: - throw std::string (STRING_VARIANT_MOD_INT_DUR); - } - break; - - case type_real: - switch (right._type) - { - case type_boolean: - throw std::string (STRING_VARIANT_MOD_REAL_BOOL); - - case type_integer: - if (right._integer == 0) - throw std::string (STRING_VARIANT_MOD_ZERO); - _real = fmod (_real, static_cast(right._integer)); - break; - - case type_real: - if (right._real == 0) - throw std::string (STRING_VARIANT_MOD_ZERO); - _real = fmod (_real, right._real); - break; - - case type_string: - throw std::string (STRING_VARIANT_MOD_REAL_STR); - - case type_date: - throw std::string (STRING_VARIANT_MOD_REAL_DATE); - - case type_duration: - throw std::string (STRING_VARIANT_MOD_REAL_DUR); - } - break; - - case type_string: - throw std::string (STRING_VARIANT_MOD_STR); - - case type_date: - throw std::string (STRING_VARIANT_MOD_DATE); - - case type_duration: - throw std::string (STRING_VARIANT_MOD_DUR); + throw std::string(STRING_VARIANT_MOD_DUR); } return *this; } //////////////////////////////////////////////////////////////////////////////// -Variant Variant::operator% (const Variant& other) const -{ - Variant left (*this); +Variant Variant::operator%(const Variant& other) const { + Variant left(*this); left %= other; return left; } //////////////////////////////////////////////////////////////////////////////// -Variant::operator std::string () const -{ - switch (_type) - { - case type_boolean: - return std::string (_bool ? "true" : "false"); +Variant::operator std::string() const { + switch (_type) { + case type_boolean: + return std::string(_bool ? "true" : "false"); - case type_integer: - { + case type_integer: { std::stringstream s; s << _integer; - return s.str (); + return s.str(); } - case type_real: - { + case type_real: { std::stringstream s; s << _real; - return s.str (); + return s.str(); } - case type_string: - return _string; + case type_string: + return _string; - case type_date: - return Datetime (_date).toISOLocalExtended (); + case type_date: + return Datetime(_date).toISOLocalExtended(); - case type_duration: - return Duration (_duration).formatISO (); + case type_duration: + return Duration(_duration).formatISO(); } return ""; } //////////////////////////////////////////////////////////////////////////////// -void Variant::sqrt () -{ - if (_type == type_string) - Lexer::dequote (_string); +void Variant::sqrt() { + if (_type == type_string) Lexer::dequote(_string); - cast (type_real); - if (_real < 0.0) - throw std::string (STRING_VARIANT_SQRT_NEG); - _real = ::sqrt (_real); + cast(type_real); + if (_real < 0.0) throw std::string(STRING_VARIANT_SQRT_NEG); + _real = ::sqrt(_real); } //////////////////////////////////////////////////////////////////////////////// -void Variant::cast (const enum type new_type) -{ +void Variant::cast(const enum type new_type) { // Short circuit. - if (_type == new_type) - return; + if (_type == new_type) return; // From type_boolean - switch (_type) - { - case type_boolean: - switch (new_type) - { - case type_boolean: break; - case type_integer: _integer = _bool ? 1 : 0; break; - case type_real: _real = _bool ? 1.0 : 0.0; break; - case type_string: _string = _bool ? "true" : "false"; break; - case type_date: _date = _bool ? 1 : 0; break; - case type_duration: _duration = _bool ? 1 : 0; break; - } - break; - - case type_integer: - switch (new_type) - { - case type_boolean: _bool = _integer == 0 ? false : true; break; - case type_integer: break; - case type_real: _real = static_cast(_integer); break; - case type_string: - { - char temp[24]; - snprintf (temp, 24, "%lld", _integer); - _string = temp; - } - break; - case type_date: _date = (time_t) _integer; break; - case type_duration: _duration = (time_t) _integer; break; - } - break; - - case type_real: - switch (new_type) - { - case type_boolean: _bool = _real == 0.0 ? false : true; break; - case type_integer: _integer = (long long) _real; break; - case type_real: break; - case type_string: - { - char temp[24]; - snprintf (temp, 24, "%g", _real); - _string = temp; - } - break; - case type_date: _date = (time_t) (int) _real; break; - case type_duration: _duration = (time_t) (int) _real; break; - } - break; - - case type_string: - Lexer::dequote (_string); - switch (new_type) - { + switch (_type) { case type_boolean: - _bool = (_string.length () == 0 || - _string == "0" || - _string == "0.0") ? false : true; + switch (new_type) { + case type_boolean: + break; + case type_integer: + _integer = _bool ? 1 : 0; + break; + case type_real: + _real = _bool ? 1.0 : 0.0; + break; + case type_string: + _string = _bool ? "true" : "false"; + break; + case type_date: + _date = _bool ? 1 : 0; + break; + case type_duration: + _duration = _bool ? 1 : 0; + break; + } break; + case type_integer: - _integer = (long long) strtol (_string.c_str (), nullptr, (_string.substr (0, 2) == "0x" ? 16 : 10)); + switch (new_type) { + case type_boolean: + _bool = _integer == 0 ? false : true; + break; + case type_integer: + break; + case type_real: + _real = static_cast(_integer); + break; + case type_string: { + char temp[24]; + snprintf(temp, 24, "%lld", _integer); + _string = temp; + } break; + case type_date: + _date = (time_t)_integer; + break; + case type_duration: + _duration = (time_t)_integer; + break; + } break; - case type_real: _real = strtod (_string.c_str (), nullptr); break; - case type_string: break; + + case type_real: + switch (new_type) { + case type_boolean: + _bool = _real == 0.0 ? false : true; + break; + case type_integer: + _integer = (long long)_real; + break; + case type_real: + break; + case type_string: { + char temp[24]; + snprintf(temp, 24, "%g", _real); + _string = temp; + } break; + case type_date: + _date = (time_t)(int)_real; + break; + case type_duration: + _duration = (time_t)(int)_real; + break; + } + break; + + case type_string: + Lexer::dequote(_string); + switch (new_type) { + case type_boolean: + _bool = (_string.length() == 0 || _string == "0" || _string == "0.0") ? false : true; + break; + case type_integer: + _integer = + (long long)strtol(_string.c_str(), nullptr, (_string.substr(0, 2) == "0x" ? 16 : 10)); + break; + case type_real: + _real = strtod(_string.c_str(), nullptr); + break; + case type_string: + break; + case type_date: { + _date = 0; + Datetime iso; + std::string::size_type pos = 0; + if (iso.parse(_string, pos, dateFormat) && pos == _string.length()) { + _date = iso.toEpoch(); + break; + } + + pos = 0; + Duration isop; + if (isop.parse(_string, pos) && pos == _string.length()) { + _date = Datetime().toEpoch() + isop.toTime_t(); + break; + } + + if (dateFormat != "") { + _date = Datetime(_string, dateFormat).toEpoch(); + break; + } + } break; + case type_duration: { + _duration = 0; + std::string::size_type pos = 0; + Duration iso; + if (iso.parse(_string, pos) && pos == _string.length()) { + _duration = iso.toTime_t(); + } + } break; + } + break; + case type_date: - { - _date = 0; - Datetime iso; - std::string::size_type pos = 0; - if (iso.parse (_string, pos, dateFormat) && - pos == _string.length ()) - { - _date = iso.toEpoch (); + switch (new_type) { + case type_boolean: + _bool = _date != 0 ? true : false; break; - } - - pos = 0; - Duration isop; - if (isop.parse (_string, pos) && - pos == _string.length ()) - { - _date = Datetime ().toEpoch () + isop.toTime_t (); + case type_integer: + _integer = (long long)_date; break; - } - - if (dateFormat != "") - { - _date = Datetime (_string, dateFormat).toEpoch (); + case type_real: + _real = static_cast(_date); + break; + case type_string: + _string = (std::string) * this; + break; + case type_date: + break; + // TODO: Not exactly correct (should duration convert into date?), but + // currently needed for symmetry, which is assumed by operators. + case type_duration: + _duration = _date - time(nullptr); break; - } } break; + case type_duration: - { - _duration = 0; - std::string::size_type pos = 0; - Duration iso; - if (iso.parse (_string, pos) && - pos == _string.length ()) - { - _duration = iso.toTime_t (); - } + switch (new_type) { + case type_boolean: + _bool = _duration != 0 ? true : false; + break; + case type_integer: + _integer = (long long)_duration; + break; + case type_real: + _real = static_cast(_duration); + break; + case type_string: + _string = (std::string) * this; + break; + case type_date: + _date = time(nullptr) + _duration; + break; + case type_duration: + break; } break; - } - break; - - case type_date: - switch (new_type) - { - case type_boolean: _bool = _date != 0 ? true : false; break; - case type_integer: _integer = (long long) _date; break; - case type_real: _real = static_cast(_date); break; - case type_string: _string = (std::string) *this; break; - case type_date: break; - // TODO: Not exactly correct (should duration convert into date?), but - // currently needed for symmetry, which is assumed by operators. - case type_duration: _duration = _date - time (nullptr); break; - } - break; - - case type_duration: - switch (new_type) - { - case type_boolean: _bool = _duration != 0 ? true : false; break; - case type_integer: _integer = (long long) _duration; break; - case type_real: _real = static_cast(_duration); break; - case type_string: _string = (std::string) *this; break; - case type_date: _date = time (nullptr) + _duration; break; - case type_duration: break; - } - break; } _type = new_type; } //////////////////////////////////////////////////////////////////////////////// -int Variant::type () -{ - return _type; -} +int Variant::type() { return _type; } //////////////////////////////////////////////////////////////////////////////// -bool Variant::trivial () const -{ - return (_type == type_integer && _integer == 0) || - (_type == type_real && _real == 0.0) || - (_type == type_string && _string == "") || - (_type == type_date && _date == 0) || +bool Variant::trivial() const { + return (_type == type_integer && _integer == 0) || (_type == type_real && _real == 0.0) || + (_type == type_string && _string == "") || (_type == type_date && _date == 0) || (_type == type_duration && _duration == 0); } //////////////////////////////////////////////////////////////////////////////// -bool Variant::get_bool () const -{ - return _bool; -} +bool Variant::get_bool() const { return _bool; } //////////////////////////////////////////////////////////////////////////////// -long long Variant::get_integer () const -{ - return _integer; -} +long long Variant::get_integer() const { return _integer; } //////////////////////////////////////////////////////////////////////////////// -double Variant::get_real () const -{ - return _real; -} +double Variant::get_real() const { return _real; } //////////////////////////////////////////////////////////////////////////////// -const std::string& Variant::get_string () const -{ - return _string; -} +const std::string& Variant::get_string() const { return _string; } //////////////////////////////////////////////////////////////////////////////// -time_t Variant::get_date () const -{ - return _date; -} +time_t Variant::get_date() const { return _date; } //////////////////////////////////////////////////////////////////////////////// -time_t Variant::get_duration () const -{ - return _duration; -} +time_t Variant::get_duration() const { return _duration; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Variant.h b/src/Variant.h index 2970a9e61..5634473b9 100644 --- a/src/Variant.h +++ b/src/Variant.h @@ -27,94 +27,92 @@ #ifndef INCLUDED_VARIANT #define INCLUDED_VARIANT -#include -#include -#include #include -class Variant -{ -public: +#include + +class Variant { + public: static std::string dateFormat; static bool searchCaseSensitive; static bool searchUsingRegex; static bool isoEnabled; - enum type {type_boolean, type_integer, type_real, type_string, type_date, type_duration}; + enum type { type_boolean, type_integer, type_real, type_string, type_date, type_duration }; - Variant () = default; - Variant (const Variant&); - Variant (const bool); - Variant (const int); - Variant (const double); - Variant (const std::string&); - Variant (const char*); - Variant (const time_t, const enum type); + Variant() = default; + Variant(const Variant&); + Variant(const bool); + Variant(const int); + Variant(const double); + Variant(const std::string&); + Variant(const char*); + Variant(const time_t, const enum type); - void source (const std::string&); - const std::string& source () const; + void source(const std::string&); + const std::string& source() const; - Variant& operator= (const Variant&); + Variant& operator=(const Variant&); - bool operator&& (const Variant&) const; - bool operator|| (const Variant&) const; - bool operator_xor (const Variant&) const; - bool operator< (const Variant&) const; - bool operator<= (const Variant&) const; - bool operator> (const Variant&) const; - bool operator>= (const Variant&) const; - bool operator== (const Variant&) const; - bool operator!= (const Variant&) const; - bool operator_match (const Variant&, const Task&) const; - bool operator_nomatch (const Variant&, const Task&) const; - bool operator_partial (const Variant&) const; - bool operator_nopartial (const Variant&) const; - bool operator_hastag (const Variant&, const Task&) const; - bool operator_notag (const Variant&, const Task&) const; - bool operator! () const; + bool operator&&(const Variant&) const; + bool operator||(const Variant&) const; + bool operator_xor(const Variant&) const; + bool operator<(const Variant&) const; + bool operator<=(const Variant&) const; + bool operator>(const Variant&) const; + bool operator>=(const Variant&) const; + bool operator==(const Variant&) const; + bool operator!=(const Variant&) const; + bool operator_match(const Variant&, const Task&) const; + bool operator_nomatch(const Variant&, const Task&) const; + bool operator_partial(const Variant&) const; + bool operator_nopartial(const Variant&) const; + bool operator_hastag(const Variant&, const Task&) const; + bool operator_notag(const Variant&, const Task&) const; + bool operator!() const; - Variant& operator^= (const Variant&); - Variant operator^ (const Variant&) const; + Variant& operator^=(const Variant&); + Variant operator^(const Variant&) const; - Variant& operator-= (const Variant&); - Variant operator- (const Variant&) const; + Variant& operator-=(const Variant&); + Variant operator-(const Variant&) const; - Variant& operator+= (const Variant&); - Variant operator+ (const Variant&) const; + Variant& operator+=(const Variant&); + Variant operator+(const Variant&) const; - Variant& operator*= (const Variant&); - Variant operator* (const Variant&) const; + Variant& operator*=(const Variant&); + Variant operator*(const Variant&) const; - Variant& operator/= (const Variant&); - Variant operator/ (const Variant&) const; + Variant& operator/=(const Variant&); + Variant operator/(const Variant&) const; - Variant& operator%= (const Variant&); - Variant operator% (const Variant&) const; + Variant& operator%=(const Variant&); + Variant operator%(const Variant&) const; - operator std::string () const; - void sqrt (); + operator std::string() const; + void sqrt(); - void cast (const enum type); - int type (); - bool trivial () const; + void cast(const enum type); + int type(); + bool trivial() const; - bool get_bool () const; - long long get_integer () const; - double get_real () const; - const std::string& get_string () const; - time_t get_date () const; - time_t get_duration () const; + bool get_bool() const; + long long get_integer() const; + double get_real() const; + const std::string& get_string() const; + time_t get_date() const; + time_t get_duration() const; -private: - enum type _type {type_boolean}; - bool _bool {false}; - long long _integer {0}; - double _real {0.0}; - std::string _string {""}; - time_t _date {0}; - time_t _duration {0}; + private: + enum type _type { type_boolean }; + bool _bool{false}; + long long _integer{0}; + double _real{0.0}; + std::string _string{""}; + time_t _date{0}; + time_t _duration{0}; - std::string _source {""}; + std::string _source{""}; }; #endif diff --git a/src/Version.cpp b/src/Version.cpp index 56d01afb9..e1633f86c 100644 --- a/src/Version.cpp +++ b/src/Version.cpp @@ -26,6 +26,8 @@ #include #include +// cmake.h include header must come first + #include #include #include @@ -65,39 +67,32 @@ bool Version::is_valid() const { return major >= 0; } //////////////////////////////////////////////////////////////////////////////// bool Version::operator<(const Version &other) const { - return std::tie(major, minor, patch) < - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) < std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// bool Version::operator<=(const Version &other) const { - return std::tie(major, minor, patch) <= - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) <= std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// bool Version::operator>(const Version &other) const { - return std::tie(major, minor, patch) > - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) > std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// bool Version::operator>=(const Version &other) const { - return std::tie(major, minor, patch) >= - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) >= std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// bool Version::operator==(const Version &other) const { - return std::tie(major, minor, patch) == - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) == std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// bool Version::operator!=(const Version &other) const { - std::cout << other; - return std::tie(major, minor, patch) != - std::tie(other.major, other.minor, other.patch); + return std::tie(major, minor, patch) != std::tie(other.major, other.minor, other.patch); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/Version.h b/src/Version.h index 2c96d61e7..03b81d2ad 100644 --- a/src/Version.h +++ b/src/Version.h @@ -31,7 +31,7 @@ // A utility class for handling Taskwarrior versions. class Version { -public: + public: // Parse a version from a string. This must be of the format // digits.digits.digits. explicit Version(std::string version); @@ -60,9 +60,9 @@ public: // Convert back to a string. operator std::string() const; - friend std::ostream& operator<<(std::ostream& os, const Version& version); + friend std::ostream &operator<<(std::ostream &os, const Version &version); -private: + private: int major = -1; int minor = -1; int patch = -1; diff --git a/src/ViewTask.cpp b/src/ViewTask.cpp index e3adf8826..ad04e9811 100644 --- a/src/ViewTask.cpp +++ b/src/ViewTask.cpp @@ -25,42 +25,41 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include +// cmake.h include header must come first + #include +#include #include -#include +#include #include -#include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -ViewTask::ViewTask () -: _width (0) -, _left_margin (0) -, _header (0) -, _sort_header (0) -, _odd (0) -, _even (0) -, _intra_padding (1) -, _intra_odd (0) -, _intra_even (0) -, _extra_padding (0) -, _extra_odd (0) -, _extra_even (0) -, _truncate_lines (0) -, _truncate_rows (0) -, _lines (0) -, _rows (0) -{ -} +ViewTask::ViewTask() + : _width(0), + _left_margin(0), + _header(0), + _sort_header(0), + _odd(0), + _even(0), + _intra_padding(1), + _intra_odd(0), + _intra_even(0), + _extra_padding(0), + _extra_odd(0), + _extra_even(0), + _truncate_lines(0), + _truncate_rows(0), + _lines(0), + _rows(0) {} //////////////////////////////////////////////////////////////////////////////// -ViewTask::~ViewTask () -{ - for (auto& col : _columns) - delete col; +ViewTask::~ViewTask() { + for (auto& col : _columns) delete col; - _columns.clear (); + _columns.clear(); } //////////////////////////////////////////////////////////////////////////////// @@ -106,64 +105,55 @@ ViewTask::~ViewTask () // the larger fields. If the widest field is W0, and the second widest // field is W1, then a solution may be achievable by reducing W0 --> W1. // -std::string ViewTask::render (std::vector & data, std::vector & sequence) -{ +std::string ViewTask::render(std::vector& data, std::vector& sequence) { Timer timer; - bool const obfuscate = Context::getContext ().config.getBoolean ("obfuscate"); - bool const print_empty_columns = Context::getContext ().config.getBoolean ("print.empty.columns"); - std::vector nonempty_columns; - std::vector nonempty_sort; + bool const obfuscate = Context::getContext().config.getBoolean("obfuscate"); + bool const print_empty_columns = Context::getContext().config.getBoolean("print.empty.columns"); + std::vector nonempty_columns; + std::vector nonempty_sort; // Determine minimal, ideal column widths. - std::vector minimal; - std::vector ideal; + std::vector minimal; + std::vector ideal; - for (unsigned int i = 0; i < _columns.size (); ++i) - { + for (unsigned int i = 0; i < _columns.size(); ++i) { // Headers factor in to width calculations. unsigned int global_min = 0; unsigned int global_ideal = global_min; - for (unsigned int s = 0; s < sequence.size (); ++s) - { - if ((int)s >= _truncate_lines && _truncate_lines != 0) - break; + for (unsigned int s = 0; s < sequence.size(); ++s) { + if ((int)s >= _truncate_lines && _truncate_lines != 0) break; - if ((int)s >= _truncate_rows && _truncate_rows != 0) - break; + if ((int)s >= _truncate_rows && _truncate_rows != 0) break; // Determine minimum and ideal width for this column. unsigned int min = 0; unsigned int ideal = 0; - _columns[i]->measure (data[sequence[s]], min, ideal); + _columns[i]->measure(data[sequence[s]], min, ideal); - if (min > global_min) global_min = min; + if (min > global_min) global_min = min; if (ideal > global_ideal) global_ideal = ideal; // If a fixed-width column was just measured, there is no point repeating // the measurement for all tasks. - if (_columns[i]->is_fixed_width ()) - break; + if (_columns[i]->is_fixed_width()) break; } - if (print_empty_columns || global_min != 0) - { - unsigned int label_length = utf8_width (_columns[i]->label ()); - if (label_length > global_min) global_min = label_length; + if (print_empty_columns || global_min != 0) { + unsigned int label_length = utf8_width(_columns[i]->label()); + if (label_length > global_min) global_min = label_length; if (label_length > global_ideal) global_ideal = label_length; - minimal.push_back (global_min); - ideal.push_back (global_ideal); + minimal.push_back(global_min); + ideal.push_back(global_ideal); } - if (! print_empty_columns) - { - if (global_min != 0) // Column is nonempty + if (!print_empty_columns) { + if (global_min != 0) // Column is nonempty { - nonempty_columns.push_back (_columns[i]); - nonempty_sort.push_back (_sort[i]); - } - else // Column is empty, drop it + nonempty_columns.push_back(_columns[i]); + nonempty_sort.push_back(_sort[i]); + } else // Column is empty, drop it { // Note: This is safe to do because we set _columns = nonempty_columns // after iteration over _columns is finished. @@ -172,51 +162,40 @@ std::string ViewTask::render (std::vector & data, std::vector & seque } } - if (! print_empty_columns) - { + if (!print_empty_columns) { _columns = nonempty_columns; _sort = nonempty_sort; } - int all_extra = _left_margin - + (2 * _extra_padding) - + ((_columns.size () - 1) * _intra_padding); + int all_extra = _left_margin + (2 * _extra_padding) + ((_columns.size() - 1) * _intra_padding); // Sum the widths. - int sum_minimal = std::accumulate (minimal.begin (), minimal.end (), 0); - int sum_ideal = std::accumulate (ideal.begin (), ideal.end (), 0); + int sum_minimal = std::accumulate(minimal.begin(), minimal.end(), 0); + int sum_ideal = std::accumulate(ideal.begin(), ideal.end(), 0); // Calculate final column widths. int overage = _width - sum_minimal - all_extra; - Context::getContext ().debug (format ("ViewTask::render min={1} ideal={2} overage={3} width={4}", - sum_minimal + all_extra, - sum_ideal + all_extra, - overage, - _width)); + Context::getContext().debug(format("ViewTask::render min={1} ideal={2} overage={3} width={4}", + sum_minimal + all_extra, sum_ideal + all_extra, overage, + _width)); - std::vector widths; + std::vector widths; // Ideal case. Everything fits. - if (_width == 0 || sum_ideal + all_extra <= _width) - { + if (_width == 0 || sum_ideal + all_extra <= _width) { widths = ideal; } // Not enough for minimum. Decrease certain columns. - else if (overage < 0) - { + else if (overage < 0) { // Determine which columns are the longest. unsigned int longest = 0; unsigned int second_longest = 0; - for (unsigned int j = 0; j < minimal.size(); j++) - { - if (minimal[j] > minimal[longest]) - { + for (unsigned int j = 0; j < minimal.size(); j++) { + if (minimal[j] > minimal[longest]) { second_longest = longest; longest = j; - } - else if (minimal[j] > minimal[second_longest]) - { + } else if (minimal[j] > minimal[second_longest]) { second_longest = j; } } @@ -224,53 +203,46 @@ std::string ViewTask::render (std::vector & data, std::vector & seque // Case 1: Shortening longest column still keeps it longest. Let it bear // all the shortening. widths = minimal; - if (minimal[longest] + overage >= minimal[second_longest]) - widths[longest] += overage; + if (minimal[longest] + overage >= minimal[second_longest]) widths[longest] += overage; // Case 2: Shorten the longest column to second longest length. Try to // split shortening them evenly. - else - { + else { int decrease = minimal[second_longest] - minimal[longest]; widths[longest] += decrease; overage = overage - decrease; // Attempt to decrease the two longest columns (at most to two characters) - if (-overage <= widths[longest] + widths[second_longest] - 4) - { + if (-overage <= widths[longest] + widths[second_longest] - 4) { // Compute half of the overage, rounding up int half_overage = overage / 2 + overage % 2; // Decrease both larges columns by this amount widths[longest] += half_overage; widths[second_longest] += half_overage; - } - else + } else // If reducing two of the longest solumns to 2 characters is not sufficient, then give up. - Context::getContext ().error (format ("The report has a minimum width of {1} and does not fit in the available width of {2}.", sum_minimal + all_extra, _width)); + Context::getContext().error(format( + "The report has a minimum width of {1} and does not fit in the available width of {2}.", + sum_minimal + all_extra, _width)); } } // Perfect minimal width. - else if (overage == 0) - { + else if (overage == 0) { widths = minimal; } // Extra space to share. - else if (overage > 0) - { + else if (overage > 0) { widths = minimal; // Spread 'overage' among columns where width[i] < ideal[i] bool needed = true; - while (overage && needed) - { + while (overage && needed) { needed = false; - for (unsigned int i = 0; i < _columns.size () && overage; ++i) - { - if (widths[i] < ideal[i]) - { + for (unsigned int i = 0; i < _columns.size() && overage; ++i) { + if (widths[i] < ideal[i]) { ++widths[i]; --overage; needed = true; @@ -281,39 +253,34 @@ std::string ViewTask::render (std::vector & data, std::vector & seque // Compose column headers. unsigned int max_lines = 0; - std::vector > headers; - for (unsigned int c = 0; c < _columns.size (); ++c) - { - headers.emplace_back (); - _columns[c]->renderHeader (headers[c], widths[c], _sort[c] ? _sort_header : _header); + std::vector> headers; + for (unsigned int c = 0; c < _columns.size(); ++c) { + headers.emplace_back(); + _columns[c]->renderHeader(headers[c], widths[c], _sort[c] ? _sort_header : _header); - if (headers[c].size () > max_lines) - max_lines = headers[c].size (); + if (headers[c].size() > max_lines) max_lines = headers[c].size(); } // Render column headers. - std::string left_margin = std::string (_left_margin, ' '); - std::string extra = std::string (_extra_padding, ' '); - std::string intra = std::string (_intra_padding, ' '); + std::string left_margin = std::string(_left_margin, ' '); + std::string extra = std::string(_extra_padding, ' '); + std::string intra = std::string(_intra_padding, ' '); - std::string extra_odd = Context::getContext ().color () ? _extra_odd.colorize (extra) : extra; - std::string extra_even = Context::getContext ().color () ? _extra_even.colorize (extra) : extra; - std::string intra_odd = Context::getContext ().color () ? _intra_odd.colorize (intra) : intra; - std::string intra_even = Context::getContext ().color () ? _intra_even.colorize (intra) : intra; + std::string extra_odd = Context::getContext().color() ? _extra_odd.colorize(extra) : extra; + std::string extra_even = Context::getContext().color() ? _extra_even.colorize(extra) : extra; + std::string intra_odd = Context::getContext().color() ? _intra_odd.colorize(intra) : intra; + std::string intra_even = Context::getContext().color() ? _intra_even.colorize(intra) : intra; std::string out; _lines = 0; - for (unsigned int i = 0; i < max_lines; ++i) - { + for (unsigned int i = 0; i < max_lines; ++i) { out += left_margin + extra; - for (unsigned int c = 0; c < _columns.size (); ++c) - { - if (c) - out += intra; + for (unsigned int c = 0; c < _columns.size(); ++c) { + if (c) out += intra; - if (headers[c].size () < max_lines - i) - out += _header.colorize (std::string (widths[c], ' ')); + if (headers[c].size() < max_lines - i) + out += _header.colorize(std::string(widths[c], ' ')); else out += headers[c][i]; } @@ -321,60 +288,50 @@ std::string ViewTask::render (std::vector & data, std::vector & seque out += extra; // Trim right. - out.erase (out.find_last_not_of (' ') + 1); + out.erase(out.find_last_not_of(' ') + 1); out += "\n"; // Stop if the line limit is exceeded. - if (++_lines >= _truncate_lines && _truncate_lines != 0) - { - Context::getContext ().time_render_us += timer.total_us (); + if (++_lines >= _truncate_lines && _truncate_lines != 0) { + Context::getContext().time_render_us += timer.total_us(); return out; } } // Compose, render columns, in sequence. _rows = 0; - std::vector > cells; - for (unsigned int s = 0; s < sequence.size (); ++s) - { + std::vector> cells; + for (unsigned int s = 0; s < sequence.size(); ++s) { max_lines = 0; // Apply color rules to task. Color rule_color; - autoColorize (data[sequence[s]], rule_color); + autoColorize(data[sequence[s]], rule_color); // Alternate rows based on |s % 2| bool odd = (s % 2) ? true : false; Color row_color; - if (Context::getContext ().color ()) - { + if (Context::getContext().color()) { row_color = odd ? _odd : _even; - row_color.blend (rule_color); + row_color.blend(rule_color); } - for (unsigned int c = 0; c < _columns.size (); ++c) - { - cells.emplace_back (); - _columns[c]->render (cells[c], data[sequence[s]], widths[c], row_color); + for (unsigned int c = 0; c < _columns.size(); ++c) { + cells.emplace_back(); + _columns[c]->render(cells[c], data[sequence[s]], widths[c], row_color); - if (cells[c].size () > max_lines) - max_lines = cells[c].size (); + if (cells[c].size() > max_lines) max_lines = cells[c].size(); if (obfuscate) - if (_columns[c]->type () == "string") - for (auto& line : cells[c]) - line = obfuscateText (line); + if (_columns[c]->type() == "string") + for (auto& line : cells[c]) line = obfuscateText(line); } // Listing breaks are simply blank lines inserted when a column value // changes. - if (s > 0 && - _breaks.size () > 0) - { - for (const auto& b : _breaks) - { - if (data[sequence[s - 1]].get (b) != data[sequence[s]].get (b)) - { + if (s > 0 && _breaks.size() > 0) { + for (const auto& b : _breaks) { + if (data[sequence[s - 1]].get(b) != data[sequence[s]].get(b)) { out += "\n"; ++_lines; @@ -384,51 +341,46 @@ std::string ViewTask::render (std::vector & data, std::vector & seque } } - for (unsigned int i = 0; i < max_lines; ++i) - { + for (unsigned int i = 0; i < max_lines; ++i) { out += left_margin + (odd ? extra_odd : extra_even); - for (unsigned int c = 0; c < _columns.size (); ++c) - { - if (c) - { - if (row_color.nontrivial ()) - row_color._colorize (out, intra); + for (unsigned int c = 0; c < _columns.size(); ++c) { + if (c) { + if (row_color.nontrivial()) + row_color._colorize(out, intra); else out += (odd ? intra_odd : intra_even); } - if (i < cells[c].size ()) + if (i < cells[c].size()) out += cells[c][i]; else - row_color._colorize (out, std::string (widths[c], ' ')); + row_color._colorize(out, std::string(widths[c], ' ')); } out += (odd ? extra_odd : extra_even); // Trim right. - out.erase (out.find_last_not_of (' ') + 1); + out.erase(out.find_last_not_of(' ') + 1); out += "\n"; // Stop if the line limit is exceeded. - if (++_lines >= _truncate_lines && _truncate_lines != 0) - { - Context::getContext ().time_render_us += timer.total_us (); + if (++_lines >= _truncate_lines && _truncate_lines != 0) { + Context::getContext().time_render_us += timer.total_us(); return out; } } - cells.clear (); + cells.clear(); // Stop if the row limit is exceeded. - if (++_rows >= _truncate_rows && _truncate_rows != 0) - { - Context::getContext ().time_render_us += timer.total_us (); + if (++_rows >= _truncate_rows && _truncate_rows != 0) { + Context::getContext().time_render_us += timer.total_us(); return out; } } - Context::getContext ().time_render_us += timer.total_us (); + Context::getContext().time_render_us += timer.total_us(); return out; } diff --git a/src/ViewTask.h b/src/ViewTask.h index e71bc95bf..b606ec992 100644 --- a/src/ViewTask.h +++ b/src/ViewTask.h @@ -27,63 +27,68 @@ #ifndef INCLUDED_VIEWTASK #define INCLUDED_VIEWTASK -#include -#include -#include #include #include +#include -class ViewTask -{ -public: - ViewTask (); - ~ViewTask (); +#include +#include + +class ViewTask { + public: + ViewTask(); + ~ViewTask(); // View specifications. - void add (Column* column, bool sort = false) { _columns.push_back (column); _sort.push_back (sort); } - void width (int width) { _width = width; } - void leftMargin (int margin) { _left_margin = margin; } - void colorHeader (Color& c) { _header = c; if (!_sort_header) _sort_header = c; } - void colorSortHeader (Color& c) { _sort_header = c; } - void colorOdd (Color& c) { _odd = c; } - void colorEven (Color& c) { _even = c; } - void intraPadding (int padding) { _intra_padding = padding; } - void intraColorOdd (Color& c) { _intra_odd = c; } - void intraColorEven (Color& c) { _intra_even = c; } - void extraPadding (int padding) { _extra_padding = padding; } - void extraColorOdd (Color& c) { _extra_odd = c; } - void extraColorEven (Color& c) { _extra_even = c; } - void truncateLines (int n) { _truncate_lines = n; } - void truncateRows (int n) { _truncate_rows = n; } - void addBreak (const std::string& attr) { _breaks.push_back (attr); } - int lines () { return _lines; } - int rows () { return _rows; } + void add(Column* column, bool sort = false) { + _columns.push_back(column); + _sort.push_back(sort); + } + void width(int width) { _width = width; } + void leftMargin(int margin) { _left_margin = margin; } + void colorHeader(Color& c) { + _header = c; + if (!_sort_header) _sort_header = c; + } + void colorSortHeader(Color& c) { _sort_header = c; } + void colorOdd(Color& c) { _odd = c; } + void colorEven(Color& c) { _even = c; } + void intraPadding(int padding) { _intra_padding = padding; } + void intraColorOdd(Color& c) { _intra_odd = c; } + void intraColorEven(Color& c) { _intra_even = c; } + void extraPadding(int padding) { _extra_padding = padding; } + void extraColorOdd(Color& c) { _extra_odd = c; } + void extraColorEven(Color& c) { _extra_even = c; } + void truncateLines(int n) { _truncate_lines = n; } + void truncateRows(int n) { _truncate_rows = n; } + void addBreak(const std::string& attr) { _breaks.push_back(attr); } + int lines() { return _lines; } + int rows() { return _rows; } // View rendering. - std::string render (std::vector &, std::vector &); + std::string render(std::vector&, std::vector&); -private: - std::vector _columns; - std::vector _sort; - std::vector _breaks; - int _width; - int _left_margin; - Color _header; - Color _sort_header; - Color _odd; - Color _even; - int _intra_padding; - Color _intra_odd; - Color _intra_even; - int _extra_padding; - Color _extra_odd; - Color _extra_even; - int _truncate_lines; - int _truncate_rows; - int _lines; - int _rows; + private: + std::vector _columns; + std::vector _sort; + std::vector _breaks; + int _width; + int _left_margin; + Color _header; + Color _sort_header; + Color _odd; + Color _even; + int _intra_padding; + Color _intra_odd; + Color _intra_even; + int _extra_padding; + Color _extra_odd; + Color _extra_even; + int _truncate_lines; + int _truncate_rows; + int _lines; + int _rows; }; #endif //////////////////////////////////////////////////////////////////////////////// - diff --git a/src/cal b/src/cal deleted file mode 120000 index c077ac0c7..000000000 --- a/src/cal +++ /dev/null @@ -1 +0,0 @@ -task \ No newline at end of file diff --git a/src/calc.cpp b/src/calc.cpp index e6dd9ef22..4ff490abd 100644 --- a/src/calc.cpp +++ b/src/calc.cpp @@ -25,57 +25,55 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include -#include #include #include -#include +#include +#include #include +#include +#include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// // Constants. -bool get (const std::string&, Variant&) -{ -/* - // An example, although a bad one because this is supported by default. - if (name == "pi") {value = Variant (3.14159165); return true;} -*/ +bool get(const std::string&, Variant&) { + /* + // An example, although a bad one because this is supported by default. + if (name == "pi") {value = Variant (3.14159165); return true;} + */ return false; } //////////////////////////////////////////////////////////////////////////////// -int main (int argc, char** argv) -{ +int main(int argc, char** argv) { int status = 0; - try - { + try { Context globalContext; - Context::setContext (&globalContext); + Context::setContext(&globalContext); // Same operating parameters as Context::staticInitialization. - Datetime::standaloneDateEnabled = false; - Datetime::standaloneTimeEnabled = false; + Datetime::standaloneDateEnabled = false; + Datetime::standaloneTimeEnabled = false; Duration::standaloneSecondsEnabled = false; - bool infix {true}; + bool infix{true}; // Add a source for constants. Eval e; - e.addSource (get); + e.addSource(get); // Combine all the arguments into one expression string. std::string expression; - for (int i = 1; i < argc; i++) - { - if (!strcmp (argv[i], "-h") || ! strcmp (argv[i], "--help")) - { + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { std::cout << '\n' << "Usage: " << argv[0] << " [options] ''\n" << '\n' @@ -85,56 +83,47 @@ int main (int argc, char** argv) << " -i|--infix Infix expression (default)\n" << " -p|--postfix Postfix expression\n" << '\n'; - exit (1); - } - else if (!strcmp (argv[i], "-v") || !strcmp (argv[i], "--version")) - { + exit(1); + } else if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--version")) { std::cout << '\n' - << format ("calc {1} built for ", VERSION) - << osName () + << format("calc {1} built for ", VERSION) << osName() << '\n' + << "Copyright (C) 2006 - 2021 T. Babej, P. Beckingham, F. Hernandez." << '\n' << '\n' - << "Copyright (C) 2006 - 2021 T. Babej, P. Beckingham, F. Hernandez." - << '\n' - << '\n' - << "Taskwarrior may be copied only under the terms of the MIT license, which may be found in the Taskwarrior source kit." + << "Taskwarrior may be copied only under the terms of the MIT license, which may " + "be found in the Taskwarrior source kit." << '\n' << '\n'; - exit (1); - } - else if (!strcmp (argv[i], "-d") || !strcmp (argv[i], "--debug")) - e.debug (true); - else if (!strcmp (argv[i], "-i") || !strcmp (argv[i], "--infix")) + exit(1); + } else if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--debug")) + e.debug(true); + else if (!strcmp(argv[i], "-i") || !strcmp(argv[i], "--infix")) infix = true; - else if (!strcmp (argv[i], "-p") || !strcmp (argv[i], "--postfix")) + else if (!strcmp(argv[i], "-p") || !strcmp(argv[i], "--postfix")) infix = false; else - expression += std::string (argv[i]) + ' '; + expression += std::string(argv[i]) + ' '; } Variant result; if (infix) - e.evaluateInfixExpression (expression, result); + e.evaluateInfixExpression(expression, result); else - e.evaluatePostfixExpression (expression, result); + e.evaluatePostfixExpression(expression, result); // Show any debug output. - for (const auto& i : Context::getContext ().debugMessages) - std::cout << i << '\n'; + for (const auto& i : Context::getContext().debugMessages) std::cout << i << '\n'; // Show the result in string form. - std::cout << (std::string) result - << '\n'; + std::cout << (std::string)result << '\n'; } - catch (const std::string& error) - { + catch (const std::string& error) { std::cerr << error << '\n'; status = -1; } - catch (...) - { + catch (...) { std::cerr << "Unknown error occured. Oops.\n"; status = -2; } diff --git a/src/calendar b/src/calendar deleted file mode 120000 index c077ac0c7..000000000 --- a/src/calendar +++ /dev/null @@ -1 +0,0 @@ -task \ No newline at end of file diff --git a/src/columns/CMakeLists.txt b/src/columns/CMakeLists.txt index f667aac33..575d7c660 100644 --- a/src/columns/CMakeLists.txt +++ b/src/columns/CMakeLists.txt @@ -1,11 +1,9 @@ cmake_minimum_required (VERSION 3.22) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/tc ${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/libshared/src - ${CMAKE_SOURCE_DIR}/taskchampion/lib ${TASK_INCLUDE_DIRS}) set (columns_SRCS Column.cpp Column.h @@ -39,6 +37,7 @@ set (columns_SRCS Column.cpp Column.h ColWait.cpp ColWait.h) add_library (columns STATIC ${columns_SRCS}) +target_link_libraries(columns taskchampion-cpp) #SET(CMAKE_BUILD_TYPE gcov) #SET(CMAKE_CXX_FLAGS_GCOV "--coverage") diff --git a/src/columns/ColDepends.cpp b/src/columns/ColDepends.cpp index 4b69e4d39..446737d6d 100644 --- a/src/columns/ColDepends.cpp +++ b/src/columns/ColDepends.cpp @@ -25,31 +25,26 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include -#include #include +#include #include -#include #include -#include + #include #define STRING_COLUMN_LABEL_DEP "Depends" //////////////////////////////////////////////////////////////////////////////// -ColumnDepends::ColumnDepends () -{ - _name = "depends"; - _style = "list"; - _label = STRING_COLUMN_LABEL_DEP; - _styles = {"list", - "count", - "indicator"}; - _examples = {"1 2 10", - "[3]", - Context::getContext ().config.get ("dependency.indicator")}; +ColumnDepends::ColumnDepends() { + _name = "depends"; + _style = "list"; + _label = STRING_COLUMN_LABEL_DEP; + _styles = {"list", "count", "indicator"}; + _examples = {"1 2 10", "[3]", Context::getContext().config.get("dependency.indicator")}; _hyphenate = false; } @@ -57,156 +52,131 @@ ColumnDepends::ColumnDepends () //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnDepends::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnDepends::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "indicator" && _label == STRING_COLUMN_LABEL_DEP) _label = _label.substr (0, Context::getContext ().config.get ("dependency.indicator").length ()); - else if (_style == "count" && _label == STRING_COLUMN_LABEL_DEP) _label = "Dep"; + if (_style == "indicator" && _label == STRING_COLUMN_LABEL_DEP) + _label = _label.substr(0, Context::getContext().config.get("dependency.indicator").length()); + else if (_style == "count" && _label == STRING_COLUMN_LABEL_DEP) + _label = "Dep"; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnDepends::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnDepends::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - auto deptasks = task.getDependencyTasks (); + auto deptasks = task.getDependencyTasks(); - if (deptasks.size () > 0) - { - if (_style == "indicator") - { - minimum = maximum = utf8_width (Context::getContext ().config.get ("dependency.indicator")); + if (deptasks.size() > 0) { + if (_style == "indicator") { + minimum = maximum = utf8_width(Context::getContext().config.get("dependency.indicator")); } - else if (_style == "count") - { - minimum = maximum = 2 + format ((int) deptasks.size ()).length (); + else if (_style == "count") { + minimum = maximum = 2 + format((int)deptasks.size()).length(); } - else if (_style == "default" || - _style == "list") - { + else if (_style == "default" || _style == "list") { minimum = maximum = 0; - std::vector blocking_ids; + std::vector blocking_ids; blocking_ids.reserve(deptasks.size()); - for (auto& i : deptasks) - blocking_ids.push_back (i.id); + for (auto& i : deptasks) blocking_ids.push_back(i.id); - auto all = join (" ", blocking_ids); - maximum = all.length (); + auto all = join(" ", blocking_ids); + maximum = all.length(); unsigned int length; - for (auto& i : deptasks) - { - length = format (i.id).length (); - if (length > minimum) - minimum = length; + for (auto& i : deptasks) { + length = format(i.id).length(); + if (length > minimum) minimum = length; } } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnDepends::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - auto deptasks = task.getDependencyTasks (); +void ColumnDepends::render(std::vector& lines, Task& task, int width, Color& color) { + auto deptasks = task.getDependencyTasks(); - if (deptasks.size () > 0) - { - if (_style == "indicator") - { - renderStringRight (lines, width, color, Context::getContext ().config.get ("dependency.indicator")); + if (deptasks.size() > 0) { + if (_style == "indicator") { + renderStringRight(lines, width, color, + Context::getContext().config.get("dependency.indicator")); } - else if (_style == "count") - { - renderStringRight (lines, width, color, '[' + format (static_cast (deptasks.size ())) + ']'); + else if (_style == "count") { + renderStringRight(lines, width, color, '[' + format(static_cast(deptasks.size())) + ']'); } - else if (_style == "default" || - _style == "list") - { - std::vector blocking_ids; + else if (_style == "default" || _style == "list") { + std::vector blocking_ids; blocking_ids.reserve(deptasks.size()); - for (const auto& t : deptasks) - blocking_ids.push_back (t.id); + for (const auto& t : deptasks) blocking_ids.push_back(t.id); - auto combined = join (" ", blocking_ids); + auto combined = join(" ", blocking_ids); - std::vector all; - wrapText (all, combined, width, _hyphenate); + std::vector all; + wrapText(all, combined, width, _hyphenate); - for (const auto& i : all) - renderStringLeft (lines, width, color, i); + for (const auto& i : all) renderStringLeft(lines, width, color, i); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnDepends::modify (Task& task, const std::string& value) -{ +void ColumnDepends::modify(Task& task, const std::string& value) { // Apply or remove dendencies in turn. - for (auto& dep : split (value, ',')) - { + for (auto& dep : split(value, ',')) { bool removal = false; - if (dep[0] == '-') - { + if (dep[0] == '-') { removal = true; dep = dep.substr(1); } - auto hyphen = dep.find ('-'); - long lower, upper; // For ID ranges - std::regex valid_uuid ("[a-f0-9]{8}([a-f0-9-]{4,28})?"); // TODO: Make more precise + auto hyphen = dep.find('-'); + long lower, upper; // For ID ranges + std::regex valid_uuid("[a-f0-9]{8}([a-f0-9-]{4,28})?"); // TODO: Make more precise // UUID - if (dep.length () >= 8 && std::regex_match (dep, valid_uuid)) - { - // Full UUID, can be added directly - if (dep.length () == 36) - if (removal) - task.removeDependency (dep); - else - task.addDependency (dep); + if (dep.length() >= 8 && std::regex_match(dep, valid_uuid)) { + // Full UUID, can be added directly + if (dep.length() == 36) + if (removal) + task.removeDependency(dep); + else + task.addDependency(dep); - // Short UUID, need to look up full form - else - { - Task loaded_task; - if (Context::getContext ().tdb2.get (dep, loaded_task)) - if (removal) - task.removeDependency (loaded_task.get ("uuid")); - else - task.addDependency (loaded_task.get ("uuid")); - else - throw format ("Dependency could not be set - task with UUID '{1}' does not exist.", dep); - } + // Short UUID, need to look up full form + else { + Task loaded_task; + if (Context::getContext().tdb2.get(dep, loaded_task)) + if (removal) + task.removeDependency(loaded_task.get("uuid")); + else + task.addDependency(loaded_task.get("uuid")); + else + throw format("Dependency could not be set - task with UUID '{1}' does not exist.", dep); + } } // ID range - else if (dep.find ('-') != std::string::npos && - extractLongInteger (dep.substr (0, hyphen), lower) && - extractLongInteger (dep.substr (hyphen + 1), upper)) - { + else if (dep.find('-') != std::string::npos && + extractLongInteger(dep.substr(0, hyphen), lower) && + extractLongInteger(dep.substr(hyphen + 1), upper)) { for (long i = lower; i <= upper; i++) - if (removal) - task.removeDependency (i); - else - task.addDependency (i); + if (removal) + task.removeDependency(i); + else + task.addDependency(i); } // Simple ID - else if (extractLongInteger (dep, lower)) + else if (extractLongInteger(dep, lower)) if (removal) - task.removeDependency (lower); + task.removeDependency(lower); else - task.addDependency (lower); + task.addDependency(lower); else - throw format ("Invalid dependency value: '{1}'", dep); + throw format("Invalid dependency value: '{1}'", dep); } } diff --git a/src/columns/ColDepends.h b/src/columns/ColDepends.h index bf36c5b03..ef79b8f06 100644 --- a/src/columns/ColDepends.h +++ b/src/columns/ColDepends.h @@ -29,17 +29,16 @@ #include -class ColumnDepends : public ColumnTypeString -{ -public: - ColumnDepends (); +class ColumnDepends : public ColumnTypeString { + public: + ColumnDepends(); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); - void modify (Task&, const std::string&); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); + void modify(Task&, const std::string&); -private: + private: bool _hyphenate; }; diff --git a/src/columns/ColDescription.cpp b/src/columns/ColDescription.cpp index 86ef52524..460465cff 100644 --- a/src/columns/ColDescription.cpp +++ b/src/columns/ColDescription.cpp @@ -25,234 +25,191 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include #include +#include +#include #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnDescription::ColumnDescription () -{ - _name = "description"; - _style = "combined"; - _label = "Description"; +ColumnDescription::ColumnDescription() { + _name = "description"; + _style = "combined"; + _label = "Description"; _modifiable = true; - _styles = {"combined", - "desc", - "oneline", - "truncated", - "count", - "truncated_count"}; + _styles = {"combined", "desc", "oneline", "truncated", "count", "truncated_count"}; - _dateformat = Context::getContext ().config.get ("dateformat.annotation"); - if (_dateformat == "") - _dateformat = Context::getContext ().config.get ("dateformat"); + _dateformat = Context::getContext().config.get("dateformat.annotation"); + if (_dateformat == "") _dateformat = Context::getContext().config.get("dateformat"); - std::string t = Datetime ().toString (_dateformat); - std::string d = "Move your clothes down on to the lower peg"; + std::string t = Datetime().toString(_dateformat); + std::string d = "Move your clothes down on to the lower peg"; std::string a1 = "Immediately before your lunch"; std::string a2 = "If you are playing in the match this afternoon"; std::string a3 = "Before you write your letter home"; std::string a4 = "If you're not getting your hair cut"; - _examples = {d + "\n " + t + ' ' + a1 - + "\n " + t + ' ' + a2 - + "\n " + t + ' ' + a3 - + "\n " + t + ' ' + a4, - d, - d + ' ' + t + ' ' + a1 - + ' ' + t + ' ' + a2 - + ' ' + t + ' ' + a3 - + ' ' + t + ' ' + a4, - d.substr (0, 20) + "...", - d + " [4]", - d.substr (0, 20) + "... [4]"}; + _examples = { + d + "\n " + t + ' ' + a1 + "\n " + t + ' ' + a2 + "\n " + t + ' ' + a3 + "\n " + t + ' ' + + a4, + d, + d + ' ' + t + ' ' + a1 + ' ' + t + ' ' + a2 + ' ' + t + ' ' + a3 + ' ' + t + ' ' + a4, + d.substr(0, 20) + "...", + d + " [4]", + d.substr(0, 20) + "... [4]"}; - _hyphenate = Context::getContext ().config.getBoolean ("hyphenate"); + _hyphenate = Context::getContext().config.getBoolean("hyphenate"); - _indent = Context::getContext ().config.getInteger ("indent.annotation"); + _indent = Context::getContext().config.getInteger("indent.annotation"); } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnDescription::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ - std::string description = task.get (_name); +void ColumnDescription::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { + std::string description = task.get(_name); // The text // // ... - if (_style == "default" || - _style == "combined") - { - minimum = longestWord (description); - maximum = utf8_width (description); + if (_style == "default" || _style == "combined") { + minimum = longestWord(description); + maximum = utf8_width(description); - if (task.annotation_count) - { - unsigned int min_anno = _indent + Datetime::length (_dateformat); - if (min_anno > minimum) - minimum = min_anno; + if (task.annotation_count) { + unsigned int min_anno = _indent + Datetime::length(_dateformat); + if (min_anno > minimum) minimum = min_anno; - for (auto& i : task.getAnnotations ()) - { - unsigned int len = min_anno + 1 + utf8_width (i.second); - if (len > maximum) - maximum = len; + for (auto& i : task.getAnnotations()) { + unsigned int len = min_anno + 1 + utf8_width(i.second); + if (len > maximum) maximum = len; } } } // Just the text - else if (_style == "desc") - { - maximum = utf8_width (description); - minimum = longestWord (description); + else if (_style == "desc") { + maximum = utf8_width(description); + minimum = longestWord(description); } // The text ... - else if (_style == "oneline") - { - minimum = longestWord (description); - maximum = utf8_width (description); + else if (_style == "oneline") { + minimum = longestWord(description); + maximum = utf8_width(description); - if (task.annotation_count) - { - auto min_anno = Datetime::length (_dateformat); - for (auto& i : task.getAnnotations ()) - maximum += min_anno + 1 + utf8_width (i.second); + if (task.annotation_count) { + auto min_anno = Datetime::length(_dateformat); + for (auto& i : task.getAnnotations()) maximum += min_anno + 1 + utf8_width(i.second); } } // The te... - else if (_style == "truncated") - { + else if (_style == "truncated") { minimum = 4; - maximum = utf8_width (description); + maximum = utf8_width(description); } // The text [2] - else if (_style == "count") - { + else if (_style == "count") { // + ' ' + '[' + + ']' - maximum = utf8_width (description) + 1 + 1 + format (task.annotation_count).length () + 1; - minimum = longestWord (description); + maximum = utf8_width(description) + 1 + 1 + format(task.annotation_count).length() + 1; + minimum = longestWord(description); } // The te... [2] - else if (_style == "truncated_count") - { + else if (_style == "truncated_count") { minimum = 4; - maximum = utf8_width (description) + 1 + 1 + format (task.annotation_count).length () + 1; + maximum = utf8_width(description) + 1 + 1 + format(task.annotation_count).length() + 1; } } //////////////////////////////////////////////////////////////////////////////// -void ColumnDescription::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - std::string description = task.get (_name); +void ColumnDescription::render(std::vector& lines, Task& task, int width, + Color& color) { + std::string description = task.get(_name); // This is a description // // ... - if (_style == "default" || - _style == "combined") - { - if (task.annotation_count) - { - for (const auto& i : task.getAnnotations ()) - { - Datetime dt (strtoll (i.first.substr (11).c_str (), nullptr, 10)); - description += '\n' + std::string (_indent, ' ') + dt.toString (_dateformat) + ' ' + i.second; + if (_style == "default" || _style == "combined") { + if (task.annotation_count) { + for (const auto& i : task.getAnnotations()) { + Datetime dt(strtoll(i.first.substr(11).c_str(), nullptr, 10)); + description += '\n' + std::string(_indent, ' ') + dt.toString(_dateformat) + ' ' + i.second; } } - std::vector raw; - wrapText (raw, description, width, _hyphenate); + std::vector raw; + wrapText(raw, description, width, _hyphenate); - for (const auto& i : raw) - renderStringLeft (lines, width, color, i); + for (const auto& i : raw) renderStringLeft(lines, width, color, i); } // This is a description - else if (_style == "desc") - { - std::vector raw; - wrapText (raw, description, width, _hyphenate); + else if (_style == "desc") { + std::vector raw; + wrapText(raw, description, width, _hyphenate); - for (const auto& i : raw) - renderStringLeft (lines, width, color, i); + for (const auto& i : raw) renderStringLeft(lines, width, color, i); } // This is a description ... - else if (_style == "oneline") - { - if (task.annotation_count) - { - for (const auto& i : task.getAnnotations ()) - { - Datetime dt (strtoll (i.first.substr (11).c_str (), nullptr, 10)); - description += ' ' + dt.toString (_dateformat) + ' ' + i.second; + else if (_style == "oneline") { + if (task.annotation_count) { + for (const auto& i : task.getAnnotations()) { + Datetime dt(strtoll(i.first.substr(11).c_str(), nullptr, 10)); + description += ' ' + dt.toString(_dateformat) + ' ' + i.second; } } - std::vector raw; - wrapText (raw, description, width, _hyphenate); + std::vector raw; + wrapText(raw, description, width, _hyphenate); - for (const auto& i : raw) - renderStringLeft (lines, width, color, i); + for (const auto& i : raw) renderStringLeft(lines, width, color, i); } // This is a des... - else if (_style == "truncated") - { - int len = utf8_width (description); + else if (_style == "truncated") { + int len = utf8_width(description); if (len > width) - renderStringLeft (lines, width, color, description.substr (0, width - 3) + "..."); + renderStringLeft(lines, width, color, description.substr(0, width - 3) + "..."); else - renderStringLeft (lines, width, color, description); + renderStringLeft(lines, width, color, description); } // This is a description [2] - else if (_style == "count") - { - if (task.annotation_count) - description += " [" + format (task.annotation_count) + ']'; + else if (_style == "count") { + if (task.annotation_count) description += " [" + format(task.annotation_count) + ']'; - std::vector raw; - wrapText (raw, description, width, _hyphenate); + std::vector raw; + wrapText(raw, description, width, _hyphenate); - for (const auto& i : raw) - renderStringLeft (lines, width, color, i); + for (const auto& i : raw) renderStringLeft(lines, width, color, i); } // This is a des... [2] - else if (_style == "truncated_count") - { - int len = utf8_width (description); + else if (_style == "truncated_count") { + int len = utf8_width(description); std::string annos_count; int len_annos = 0; - if (task.annotation_count) - { - annos_count = " [" + format (task.annotation_count) + ']'; - len_annos = utf8_width (annos_count); + if (task.annotation_count) { + annos_count = " [" + format(task.annotation_count) + ']'; + len_annos = utf8_width(annos_count); len += len_annos; } if (len > width) - renderStringLeft (lines, width, color, description.substr (0, width - len_annos - 3) + "..." + annos_count); + renderStringLeft(lines, width, color, + description.substr(0, width - len_annos - 3) + "..." + annos_count); else - renderStringLeft (lines, width, color, description + annos_count); + renderStringLeft(lines, width, color, description + annos_count); } } diff --git a/src/columns/ColDescription.h b/src/columns/ColDescription.h index 0e1edb177..0178316a5 100644 --- a/src/columns/ColDescription.h +++ b/src/columns/ColDescription.h @@ -29,14 +29,13 @@ #include -class ColumnDescription : public ColumnTypeString -{ -public: - ColumnDescription (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnDescription : public ColumnTypeString { + public: + ColumnDescription(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: bool _hyphenate; std::string _dateformat; int _indent; diff --git a/src/columns/ColDue.cpp b/src/columns/ColDue.cpp index 3801af6d8..40a23fab2 100644 --- a/src/columns/ColDue.cpp +++ b/src/columns/ColDue.cpp @@ -25,25 +25,24 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnDue::ColumnDue () -{ - _name = "due"; +ColumnDue::ColumnDue() { + _name = "due"; _modifiable = true; - _label = "Due"; + _label = "Due"; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnDue::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnDue::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "countdown" && _label == "Due") - _label = "Count"; + if (_style == "countdown" && _label == "Due") _label = "Count"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColDue.h b/src/columns/ColDue.h index 754ee06c7..4a35f378f 100644 --- a/src/columns/ColDue.h +++ b/src/columns/ColDue.h @@ -29,11 +29,10 @@ #include -class ColumnDue : public ColumnTypeDate -{ -public: - ColumnDue (); - void setStyle (const std::string&); +class ColumnDue : public ColumnTypeDate { + public: + ColumnDue(); + void setStyle(const std::string&); }; #endif diff --git a/src/columns/ColEnd.cpp b/src/columns/ColEnd.cpp index 0f38d85ff..7b838a149 100644 --- a/src/columns/ColEnd.cpp +++ b/src/columns/ColEnd.cpp @@ -25,12 +25,13 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnEnd::ColumnEnd () -{ - _name = "end"; +ColumnEnd::ColumnEnd() { + _name = "end"; _label = "Completed"; } diff --git a/src/columns/ColEnd.h b/src/columns/ColEnd.h index 387c16a43..4a19bbad6 100644 --- a/src/columns/ColEnd.h +++ b/src/columns/ColEnd.h @@ -29,10 +29,9 @@ #include -class ColumnEnd : public ColumnTypeDate -{ -public: - ColumnEnd (); +class ColumnEnd : public ColumnTypeDate { + public: + ColumnEnd(); }; #endif diff --git a/src/columns/ColEntry.cpp b/src/columns/ColEntry.cpp index 0d3397d02..86670a1fd 100644 --- a/src/columns/ColEntry.cpp +++ b/src/columns/ColEntry.cpp @@ -25,26 +25,24 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnEntry::ColumnEntry () -{ - _name = "entry"; +ColumnEntry::ColumnEntry() { + _name = "entry"; _modifiable = true; - _label = "Added"; + _label = "Added"; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnEntry::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnEntry::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "age" && - _label == "Added") - _label = "Age"; + if (_style == "age" && _label == "Added") _label = "Age"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColEntry.h b/src/columns/ColEntry.h index ce734d1ab..72ae2ff7e 100644 --- a/src/columns/ColEntry.h +++ b/src/columns/ColEntry.h @@ -29,11 +29,10 @@ #include -class ColumnEntry : public ColumnTypeDate -{ -public: - ColumnEntry (); - void setStyle (const std::string&); +class ColumnEntry : public ColumnTypeDate { + public: + ColumnEntry(); + void setStyle(const std::string&); }; #endif diff --git a/src/columns/ColID.cpp b/src/columns/ColID.cpp index a6b8c2588..69f572434 100644 --- a/src/columns/ColID.cpp +++ b/src/columns/ColID.cpp @@ -25,49 +25,50 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include +#include //////////////////////////////////////////////////////////////////////////////// -ColumnID::ColumnID () -{ - _name = "id"; - _style = "number"; - _label = "ID"; +ColumnID::ColumnID() { + _name = "id"; + _style = "number"; + _label = "ID"; _modifiable = false; - _styles = {"number"}; - _examples = {"123"}; + _styles = {"number"}; + _examples = {"123"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnID::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnID::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { int length; - if (task.id < 10) length = 1; // Fast - else if (task.id < 100) length = 2; // Fast - else if (task.id < 1000) length = 3; // Fast - else if (task.id < 10000) length = 4; // Fast - else if (task.id < 100000) length = 5; // Fast - else length = 1 + (int) log10 ((double) task.id); // Slow + if (task.id < 10) + length = 1; // Fast + else if (task.id < 100) + length = 2; // Fast + else if (task.id < 1000) + length = 3; // Fast + else if (task.id < 10000) + length = 4; // Fast + else if (task.id < 100000) + length = 5; // Fast + else + length = 1 + (int)log10((double)task.id); // Slow minimum = maximum = length; } //////////////////////////////////////////////////////////////////////////////// -void ColumnID::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - // Completed and deleted tasks have no ID. +void ColumnID::render(std::vector& lines, Task& task, int width, Color& color) { + // Completed and deleted tasks have no ID. if (task.id) - renderInteger (lines, width, color, task.id); + renderInteger(lines, width, color, task.id); else - renderStringRight (lines, width, color, "-"); + renderStringRight(lines, width, color, "-"); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColID.h b/src/columns/ColID.h index fdec75db8..8279995a3 100644 --- a/src/columns/ColID.h +++ b/src/columns/ColID.h @@ -29,14 +29,13 @@ #include -class ColumnID : public ColumnTypeNumeric -{ -public: - ColumnID (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnID : public ColumnTypeNumeric { + public: + ColumnID(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColIMask.cpp b/src/columns/ColIMask.cpp index ac49e4250..067e8b85c 100644 --- a/src/columns/ColIMask.cpp +++ b/src/columns/ColIMask.cpp @@ -25,38 +25,31 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnIMask::ColumnIMask () -{ - _name = "imask"; - _style = "number"; - _label = "Mask Index"; +ColumnIMask::ColumnIMask() { + _name = "imask"; + _style = "number"; + _label = "Mask Index"; _modifiable = false; - _styles = {"number"}; - _examples = {"12"}; + _styles = {"number"}; + _examples = {"12"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnIMask::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnIMask::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - minimum = maximum = task.get (_name).length (); + if (task.has(_name)) minimum = maximum = task.get(_name).length(); } //////////////////////////////////////////////////////////////////////////////// -void ColumnIMask::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - renderStringRight (lines, width, color, task.get (_name)); +void ColumnIMask::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) renderStringRight(lines, width, color, task.get(_name)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColIMask.h b/src/columns/ColIMask.h index ccf5ec56b..7fdc4b07f 100644 --- a/src/columns/ColIMask.h +++ b/src/columns/ColIMask.h @@ -29,14 +29,13 @@ #include -class ColumnIMask : public ColumnTypeNumeric -{ -public: - ColumnIMask (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnIMask : public ColumnTypeNumeric { + public: + ColumnIMask(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColLast.cpp b/src/columns/ColLast.cpp index ca7b1c0be..341f4dcee 100644 --- a/src/columns/ColLast.cpp +++ b/src/columns/ColLast.cpp @@ -25,38 +25,31 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnLast::ColumnLast () -{ - _name = "last"; - _style = "number"; - _label = "Last instance"; +ColumnLast::ColumnLast() { + _name = "last"; + _style = "number"; + _label = "Last instance"; _modifiable = false; - _styles = {"number"}; - _examples = {"12"}; + _styles = {"number"}; + _examples = {"12"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnLast::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnLast::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - minimum = maximum = task.get (_name).length (); + if (task.has(_name)) minimum = maximum = task.get(_name).length(); } //////////////////////////////////////////////////////////////////////////////// -void ColumnLast::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - renderStringRight (lines, width, color, task.get (_name)); +void ColumnLast::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) renderStringRight(lines, width, color, task.get(_name)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColLast.h b/src/columns/ColLast.h index 54d17c782..8f24c291f 100644 --- a/src/columns/ColLast.h +++ b/src/columns/ColLast.h @@ -29,14 +29,13 @@ #include -class ColumnLast : public ColumnTypeNumeric -{ -public: - ColumnLast (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnLast : public ColumnTypeNumeric { + public: + ColumnLast(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColMask.cpp b/src/columns/ColMask.cpp index b1bb13870..ca5a52f92 100644 --- a/src/columns/ColMask.cpp +++ b/src/columns/ColMask.cpp @@ -25,38 +25,31 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnMask::ColumnMask () -{ - _name = "mask"; - _style = "default"; - _label = "Mask"; +ColumnMask::ColumnMask() { + _name = "mask"; + _style = "default"; + _label = "Mask"; _modifiable = false; - _styles = {"default"}; - _examples = {"++++---"}; + _styles = {"default"}; + _examples = {"++++---"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnMask::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnMask::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - minimum = maximum = task.get (_name).length (); + if (task.has(_name)) minimum = maximum = task.get(_name).length(); } //////////////////////////////////////////////////////////////////////////////// -void ColumnMask::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - renderStringLeft (lines, width, color, task.get (_name)); +void ColumnMask::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) renderStringLeft(lines, width, color, task.get(_name)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColMask.h b/src/columns/ColMask.h index 9346e4dac..b7f28a9f4 100644 --- a/src/columns/ColMask.h +++ b/src/columns/ColMask.h @@ -29,14 +29,13 @@ #include -class ColumnMask : public ColumnTypeString -{ -public: - ColumnMask (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnMask : public ColumnTypeString { + public: + ColumnMask(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColModified.cpp b/src/columns/ColModified.cpp index fa7d96564..42091e88b 100644 --- a/src/columns/ColModified.cpp +++ b/src/columns/ColModified.cpp @@ -25,12 +25,13 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnModified::ColumnModified () -{ - _name = "modified"; +ColumnModified::ColumnModified() { + _name = "modified"; _label = "Modified"; } diff --git a/src/columns/ColModified.h b/src/columns/ColModified.h index 523d2f869..06d79b213 100644 --- a/src/columns/ColModified.h +++ b/src/columns/ColModified.h @@ -29,10 +29,9 @@ #include -class ColumnModified : public ColumnTypeDate -{ -public: - ColumnModified (); +class ColumnModified : public ColumnTypeDate { + public: + ColumnModified(); }; #endif diff --git a/src/columns/ColParent.cpp b/src/columns/ColParent.cpp index 60e5ac091..e49315904 100644 --- a/src/columns/ColParent.cpp +++ b/src/columns/ColParent.cpp @@ -25,49 +25,43 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnParent::ColumnParent () -{ - _name = "parent"; - _style = "long"; - _label = "Parent task"; +ColumnParent::ColumnParent() { + _name = "parent"; + _style = "long"; + _label = "Parent task"; _modifiable = false; - _styles = {"long", "short"}; - _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; + _styles = {"long", "short"}; + _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnParent::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnParent::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default" || _style == "long") minimum = maximum = 36; - else if (_style == "short") minimum = maximum = 8; + if (task.has(_name)) { + if (_style == "default" || _style == "long") + minimum = maximum = 36; + else if (_style == "short") + minimum = maximum = 8; } } //////////////////////////////////////////////////////////////////////////////// -void ColumnParent::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { +void ColumnParent::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { // f30cb9c3-3fc0-483f-bfb2-3bf134f00694 default // f30cb9c3 short - if (_style == "default" || - _style == "long") - renderStringLeft (lines, width, color, task.get(_name)); + if (_style == "default" || _style == "long") + renderStringLeft(lines, width, color, task.get(_name)); else if (_style == "short") - renderStringLeft (lines, width, color, task.get (_name).substr (0, 8)); + renderStringLeft(lines, width, color, task.get(_name).substr(0, 8)); } } diff --git a/src/columns/ColParent.h b/src/columns/ColParent.h index 2fff8f45d..9332a729d 100644 --- a/src/columns/ColParent.h +++ b/src/columns/ColParent.h @@ -29,14 +29,13 @@ #include -class ColumnParent : public ColumnTypeString -{ -public: - ColumnParent (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnParent : public ColumnTypeString { + public: + ColumnParent(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColProject.cpp b/src/columns/ColProject.cpp index a5890f1bb..0454bb5bb 100644 --- a/src/columns/ColProject.cpp +++ b/src/columns/ColProject.cpp @@ -25,123 +25,96 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include -#include -#include #include -#include +#include +#include #include +#include #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnProject::ColumnProject () -{ - _name = "project"; - _style = "full"; - _label = "Project"; - _styles = {"full", "parent", "indented"}; - _examples = {"home.garden", - "home", - " home.garden"}; - _hyphenate = Context::getContext ().config.getBoolean ("hyphenate"); +ColumnProject::ColumnProject() { + _name = "project"; + _style = "full"; + _label = "Project"; + _styles = {"full", "parent", "indented"}; + _examples = {"home.garden", "home", " home.garden"}; + _hyphenate = Context::getContext().config.getBoolean("hyphenate"); } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnProject::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnProject::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - std::string project = task.get (_name); + if (task.has(_name)) { + std::string project = task.get(_name); - if (_style == "parent") - { - auto period = project.find ('.'); - if (period != std::string::npos) - project = project.substr (0, period); - } - else if (_style == "indented") - { - project = indentProject (project, " ", '.'); + if (_style == "parent") { + auto period = project.find('.'); + if (period != std::string::npos) project = project.substr(0, period); + } else if (_style == "indented") { + project = indentProject(project, " ", '.'); } - minimum = longestWord (project); - maximum = utf8_width (project); + minimum = longestWord(project); + maximum = utf8_width(project); } } //////////////////////////////////////////////////////////////////////////////// -void ColumnProject::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - std::string project = task.get (_name); - if (_style == "parent") - { - auto period = project.find ('.'); - if (period != std::string::npos) - project = project.substr (0, period); - } - else if (_style == "indented") - { - project = indentProject (project, " ", '.'); +void ColumnProject::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + std::string project = task.get(_name); + if (_style == "parent") { + auto period = project.find('.'); + if (period != std::string::npos) project = project.substr(0, period); + } else if (_style == "indented") { + project = indentProject(project, " ", '.'); } - std::vector raw; - wrapText (raw, project, width, _hyphenate); + std::vector raw; + wrapText(raw, project, width, _hyphenate); - for (const auto& i : raw) - renderStringLeft (lines, width, color, i); + for (const auto& i : raw) renderStringLeft(lines, width, color, i); } } //////////////////////////////////////////////////////////////////////////////// -void ColumnProject::modify (Task& task, const std::string& value) -{ +void ColumnProject::modify(Task& task, const std::string& value) { std::string label = " MODIFICATION "; // Only if it's a DOM ref, eval it first. - Lexer lexer (value); + Lexer lexer(value); std::string domRef; Lexer::Type type; - if (lexer.token (domRef, type) && - type == Lexer::Type::dom) - { - try - { + if (lexer.token(domRef, type) && type == Lexer::Type::dom) { + try { Eval e; - e.addSource (domSource); + e.addSource(domSource); Variant v; - e.evaluateInfixExpression (value, v); - task.set (_name, (std::string) v); - Context::getContext ().debug (label + _name + " <-- '" + (std::string) v + "' <-- '" + value + '\''); - } - catch (const std::string& e) - { + e.evaluateInfixExpression(value, v); + task.set(_name, (std::string)v); + Context::getContext().debug(label + _name + " <-- '" + (std::string)v + "' <-- '" + value + + '\''); + } catch (const std::string& e) { // If the expression failed because it didn't look like an expression, // simply store it as-is. - if (e == "The value is not an expression.") - { - task.set (_name, value); - Context::getContext ().debug (label + _name + " <-- '" + value + '\''); - } - else + if (e == "The value is not an expression.") { + task.set(_name, value); + Context::getContext().debug(label + _name + " <-- '" + value + '\''); + } else throw; } - } - else - { - task.set (_name, value); - Context::getContext ().debug (label + _name + " <-- '" + value + '\''); + } else { + task.set(_name, value); + Context::getContext().debug(label + _name + " <-- '" + value + '\''); } } diff --git a/src/columns/ColProject.h b/src/columns/ColProject.h index ab61584c5..ae470f3b9 100644 --- a/src/columns/ColProject.h +++ b/src/columns/ColProject.h @@ -29,15 +29,14 @@ #include -class ColumnProject : public ColumnTypeString -{ -public: - ColumnProject (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); - void modify (Task&, const std::string&); +class ColumnProject : public ColumnTypeString { + public: + ColumnProject(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); + void modify(Task&, const std::string&); -private: + private: bool _hyphenate; }; diff --git a/src/columns/ColRType.cpp b/src/columns/ColRType.cpp index fb2a0e7a8..600e2201c 100644 --- a/src/columns/ColRType.cpp +++ b/src/columns/ColRType.cpp @@ -25,74 +25,64 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include -#include #include +#include + #include //////////////////////////////////////////////////////////////////////////////// -ColumnRType::ColumnRType () -{ - _name = "rtype"; - _style = "default"; - _label = "Recurrence type"; +ColumnRType::ColumnRType() { + _name = "rtype"; + _style = "default"; + _label = "Recurrence type"; _modifiable = false; - _styles = {"default", "indicator"}; - _examples = {"periodic", "chained"}; + _styles = {"default", "indicator"}; + _examples = {"periodic", "chained"}; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnRType::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnRType::setStyle(const std::string& value) { + Column::setStyle(value); if (_style == "indicator" && _label == "Recurrence type") - _label = _label.substr (0, Context::getContext ().config.get ("rtype.indicator").length ()); + _label = _label.substr(0, Context::getContext().config.get("rtype.indicator").length()); } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnRType::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnRType::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { + if (task.has(_name)) { if (_style == "default") - minimum = maximum = task.get (_name).length (); + minimum = maximum = task.get(_name).length(); else if (_style == "indicator") minimum = maximum = 1; } } //////////////////////////////////////////////////////////////////////////////// -void ColumnRType::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { +void ColumnRType::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { if (_style == "default") - renderStringRight (lines, width, color, task.get (_name)); + renderStringRight(lines, width, color, task.get(_name)); - else if (_style == "indicator") - { - std::string value {" "}; - value[0] = toupper (task.get (_name)[0]); - renderStringRight (lines, width, color, value); + else if (_style == "indicator") { + std::string value{" "}; + value[0] = toupper(task.get(_name)[0]); + renderStringRight(lines, width, color, value); } } } //////////////////////////////////////////////////////////////////////////////// -bool ColumnRType::validate (const std::string& input) const -{ - return input == "periodic" || - input == "chained"; +bool ColumnRType::validate(const std::string& input) const { + return input == "periodic" || input == "chained"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColRType.h b/src/columns/ColRType.h index d40eae126..dc7b86ffe 100644 --- a/src/columns/ColRType.h +++ b/src/columns/ColRType.h @@ -29,16 +29,15 @@ #include -class ColumnRType : public ColumnTypeString -{ -public: - ColumnRType (); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); - bool validate (const std::string&) const; +class ColumnRType : public ColumnTypeString { + public: + ColumnRType(); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); + bool validate(const std::string&) const; -private: + private: }; #endif diff --git a/src/columns/ColRecur.cpp b/src/columns/ColRecur.cpp index 844dcb5a5..dc6f0fefc 100644 --- a/src/columns/ColRecur.cpp +++ b/src/columns/ColRecur.cpp @@ -25,104 +25,87 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include #include -#include -#include #include -#include +#include +#include #include +#include #include //////////////////////////////////////////////////////////////////////////////// -ColumnRecur::ColumnRecur () -{ - _name = "recur"; - _style = "duration"; - _label = "Recur"; +ColumnRecur::ColumnRecur() { + _name = "recur"; + _style = "duration"; + _label = "Recur"; _modifiable = true; - _styles = {"duration", "indicator"}; - _examples = {"weekly", Context::getContext ().config.get ("recurrence.indicator")}; + _styles = {"duration", "indicator"}; + _examples = {"weekly", Context::getContext().config.get("recurrence.indicator")}; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnRecur::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnRecur::setStyle(const std::string& value) { + Column::setStyle(value); if (_style == "indicator" && _label == "Recur") - _label = _label.substr (0, Context::getContext ().config.get ("recurrence.indicator").length ()); + _label = _label.substr(0, Context::getContext().config.get("recurrence.indicator").length()); } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnRecur::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnRecur::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default" || - _style == "duration") - { - minimum = maximum = Duration (task.get (_name)).formatISO ().length (); - } - else if (_style == "indicator") - { - minimum = maximum = utf8_width (Context::getContext ().config.get ("recurrence.indicator")); + if (task.has(_name)) { + if (_style == "default" || _style == "duration") { + minimum = maximum = Duration(task.get(_name)).formatISO().length(); + } else if (_style == "indicator") { + minimum = maximum = utf8_width(Context::getContext().config.get("recurrence.indicator")); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnRecur::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "default" || - _style == "duration") - renderStringRight (lines, width, color, Duration (task.get (_name)).formatISO ()); +void ColumnRecur::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + if (_style == "default" || _style == "duration") + renderStringRight(lines, width, color, Duration(task.get(_name)).formatISO()); else if (_style == "indicator") - renderStringRight (lines, width, color, Context::getContext ().config.get ("recurrence.indicator")); + renderStringRight(lines, width, color, + Context::getContext().config.get("recurrence.indicator")); } } //////////////////////////////////////////////////////////////////////////////// // The duration is stored in raw form, but it must still be valid, // and therefore is parsed first. -void ColumnRecur::modify (Task& task, const std::string& value) -{ +void ColumnRecur::modify(Task& task, const std::string& value) { // Try to evaluate 'value'. It might work. Variant evaluatedValue; - try - { + try { Eval e; - e.addSource (domSource); - e.evaluateInfixExpression (value, evaluatedValue); + e.addSource(domSource); + e.evaluateInfixExpression(value, evaluatedValue); } - catch (...) - { - evaluatedValue = Variant (value); + catch (...) { + evaluatedValue = Variant(value); } - if (evaluatedValue.type () == Variant::type_duration) - { + if (evaluatedValue.type() == Variant::type_duration) { // Store the raw value, for 'recur'. std::string label = " MODIFICATION "; - Context::getContext ().debug (label + _name + " <-- '" + value + '\''); - task.set (_name, value); - } - else - throw format ("The duration value '{1}' is not supported.", value); + Context::getContext().debug(label + _name + " <-- '" + value + '\''); + task.set(_name, value); + } else + throw format("The duration value '{1}' is not supported.", value); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColRecur.h b/src/columns/ColRecur.h index e9560dd91..3de674aa9 100644 --- a/src/columns/ColRecur.h +++ b/src/columns/ColRecur.h @@ -31,16 +31,15 @@ // This is 'string', and not 'duration' to force the value to be stored as a // raw duration, so that it can be reevaluated every time. -class ColumnRecur : public ColumnTypeString -{ -public: - ColumnRecur (); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); - void modify (Task&, const std::string&); +class ColumnRecur : public ColumnTypeString { + public: + ColumnRecur(); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); + void modify(Task&, const std::string&); -private: + private: }; #endif diff --git a/src/columns/ColScheduled.cpp b/src/columns/ColScheduled.cpp index e04cb2517..2a5ca9849 100644 --- a/src/columns/ColScheduled.cpp +++ b/src/columns/ColScheduled.cpp @@ -25,24 +25,23 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnScheduled::ColumnScheduled () -{ - _name = "scheduled"; +ColumnScheduled::ColumnScheduled() { + _name = "scheduled"; _label = "Scheduled"; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnScheduled::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnScheduled::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "countdown" && _label == "Scheduled") - _label = "Count"; + if (_style == "countdown" && _label == "Scheduled") _label = "Count"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColScheduled.h b/src/columns/ColScheduled.h index 31beec4ca..c92440212 100644 --- a/src/columns/ColScheduled.h +++ b/src/columns/ColScheduled.h @@ -29,11 +29,10 @@ #include -class ColumnScheduled : public ColumnTypeDate -{ -public: - ColumnScheduled (); - void setStyle (const std::string&); +class ColumnScheduled : public ColumnTypeDate { + public: + ColumnScheduled(); + void setStyle(const std::string&); }; #endif diff --git a/src/columns/ColStart.cpp b/src/columns/ColStart.cpp index 2c7f7bc35..4a4dcc1a4 100644 --- a/src/columns/ColStart.cpp +++ b/src/columns/ColStart.cpp @@ -25,63 +25,53 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnStart::ColumnStart () -{ - _name = "start"; +ColumnStart::ColumnStart() { + _name = "start"; _label = "Started"; - _styles.push_back ("active"); - _examples.push_back (Context::getContext ().config.get ("active.indicator")); + _styles.push_back("active"); + _examples.push_back(Context::getContext().config.get("active.indicator")); } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnStart::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnStart::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "active" && _label == "Started") - _label = "A"; + if (_style == "active" && _label == "Started") _label = "A"; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnStart::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnStart::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { + if (task.has(_name)) { if (_style == "active") - minimum = maximum = utf8_width (Context::getContext ().config.get ("active.indicator")); + minimum = maximum = utf8_width(Context::getContext().config.get("active.indicator")); else - ColumnTypeDate::measure (task, minimum, maximum); + ColumnTypeDate::measure(task, minimum, maximum); // TODO Throw on bad format. } } //////////////////////////////////////////////////////////////////////////////// -void ColumnStart::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "active") - { - if (! task.has ("end")) - renderStringRight (lines, width, color, Context::getContext ().config.get ("active.indicator")); - } - else - ColumnTypeDate::render (lines, task, width, color); +void ColumnStart::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + if (_style == "active") { + if (!task.has("end")) + renderStringRight(lines, width, color, + Context::getContext().config.get("active.indicator")); + } else + ColumnTypeDate::render(lines, task, width, color); } } diff --git a/src/columns/ColStart.h b/src/columns/ColStart.h index 569b411f7..2196ccabd 100644 --- a/src/columns/ColStart.h +++ b/src/columns/ColStart.h @@ -29,13 +29,12 @@ #include -class ColumnStart : public ColumnTypeDate -{ -public: - ColumnStart (); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnStart : public ColumnTypeDate { + public: + ColumnStart(); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); }; #endif diff --git a/src/columns/ColStatus.cpp b/src/columns/ColStatus.cpp index 02d3ccff6..1fed6d73f 100644 --- a/src/columns/ColStatus.cpp +++ b/src/columns/ColStatus.cpp @@ -25,86 +25,82 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnStatus::ColumnStatus () -{ - _name = "status"; - _style = "long"; - _label = "Status"; - _styles = {"long", "short"}; - _examples = {"Pending", - "P"}; +ColumnStatus::ColumnStatus() { + _name = "status"; + _style = "long"; + _label = "Status"; + _styles = {"long", "short"}; + _examples = {"Pending", "P"}; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnStatus::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnStatus::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "short" && _label == "Status") - _label = "St"; + if (_style == "short" && _label == "Status") _label = "St"; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnStatus::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ - Task::status status = task.getStatus (); +void ColumnStatus::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { + Task::status status = task.getStatus(); - if (_style == "default" || - _style == "long") - { + if (_style == "default" || _style == "long") { if (status == Task::pending) - minimum = maximum = utf8_width ("Pending"); + minimum = maximum = utf8_width("Pending"); else if (status == Task::deleted) - minimum = maximum = utf8_width ("Deleted"); + minimum = maximum = utf8_width("Deleted"); else if (status == Task::waiting) - minimum = maximum = utf8_width ("Waiting"); + minimum = maximum = utf8_width("Waiting"); else if (status == Task::completed) - minimum = maximum = utf8_width ("Completed"); + minimum = maximum = utf8_width("Completed"); else if (status == Task::recurring) - minimum = maximum = utf8_width ("Recurring"); - } - else if (_style == "short") + minimum = maximum = utf8_width("Recurring"); + } else if (_style == "short") minimum = maximum = 1; } //////////////////////////////////////////////////////////////////////////////// -void ColumnStatus::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - Task::status status = task.getStatus (); +void ColumnStatus::render(std::vector& lines, Task& task, int width, Color& color) { + Task::status status = task.getStatus(); std::string value; - if (_style == "default" || - _style == "long") - { - if (status == Task::pending) value = "Pending"; - else if (status == Task::completed) value = "Completed"; - else if (status == Task::deleted) value = "Deleted"; - else if (status == Task::waiting) value = "Waiting"; - else if (status == Task::recurring) value = "Recurring"; + if (_style == "default" || _style == "long") { + if (status == Task::pending) + value = "Pending"; + else if (status == Task::completed) + value = "Completed"; + else if (status == Task::deleted) + value = "Deleted"; + else if (status == Task::waiting) + value = "Waiting"; + else if (status == Task::recurring) + value = "Recurring"; } - else if (_style == "short") - { - if (status == Task::pending) value = "P"; - else if (status == Task::completed) value = "C"; - else if (status == Task::deleted) value = "D"; - else if (status == Task::waiting) value = "W"; - else if (status == Task::recurring) value = "R"; + else if (_style == "short") { + if (status == Task::pending) + value = "P"; + else if (status == Task::completed) + value = "C"; + else if (status == Task::deleted) + value = "D"; + else if (status == Task::waiting) + value = "W"; + else if (status == Task::recurring) + value = "R"; } - renderStringLeft (lines, width, color, value); + renderStringLeft(lines, width, color, value); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColStatus.h b/src/columns/ColStatus.h index 99472466b..9c6f2d9a4 100644 --- a/src/columns/ColStatus.h +++ b/src/columns/ColStatus.h @@ -29,15 +29,14 @@ #include -class ColumnStatus : public ColumnTypeString -{ -public: - ColumnStatus (); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnStatus : public ColumnTypeString { + public: + ColumnStatus(); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColTags.cpp b/src/columns/ColTags.cpp index e5d30ab57..2d0f278c0 100644 --- a/src/columns/ColTags.cpp +++ b/src/columns/ColTags.cpp @@ -25,155 +25,122 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include #include -#include +#include +#include #include +#include #include -#include + +#include //////////////////////////////////////////////////////////////////////////////// -ColumnTags::ColumnTags () -{ - _name = "tags"; - _style = "list"; - _label = "Tags"; - _styles = {"list", "indicator", "count"}; - _examples = {"home @chore next", - Context::getContext ().config.get ("tag.indicator"), - "[2]"}; +ColumnTags::ColumnTags() { + _name = "tags"; + _style = "list"; + _label = "Tags"; + _styles = {"list", "indicator", "count"}; + _examples = {"home @chore next", Context::getContext().config.get("tag.indicator"), "[2]"}; _hyphenate = false; } //////////////////////////////////////////////////////////////////////////////// // Overriden so that style <----> label are linked. // Note that you can not determine which gets called first. -void ColumnTags::setStyle (const std::string& value) -{ - Column::setStyle (value); +void ColumnTags::setStyle(const std::string& value) { + Column::setStyle(value); - if (_style == "indicator" && - _label == "Tags") - _label = _label.substr (0, Context::getContext ().config.get ("tag.indicator").length ()); + if (_style == "indicator" && _label == "Tags") + _label = _label.substr(0, Context::getContext().config.get("tag.indicator").length()); - else if (_style == "count" && - _label == "Tags") + else if (_style == "count" && _label == "Tags") _label = "Tag"; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnTags::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnTags::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "indicator") - { - minimum = maximum = utf8_width (Context::getContext ().config.get ("tag.indicator")); - } - else if (_style == "count") - { + if (task.has(_name)) { + if (_style == "indicator") { + minimum = maximum = utf8_width(Context::getContext().config.get("tag.indicator")); + } else if (_style == "count") { minimum = maximum = 3; - } - else if (_style == "default" || - _style == "list") - { - std::string tags = task.get (_name); + } else if (_style == "default" || _style == "list") { + std::string tags = task.get(_name); // Find the widest tag. - if (tags.find (',') != std::string::npos) - { - auto all = split (tags, ','); - for (const auto& tag : all) - { - auto length = utf8_width (tag); - if (length > minimum) - minimum = length; + if (tags.find(',') != std::string::npos) { + auto all = split(tags, ','); + for (const auto& tag : all) { + auto length = utf8_width(tag); + if (length > minimum) minimum = length; } - maximum = utf8_width (tags); + maximum = utf8_width(tags); } // No need to split a single tag. else - minimum = maximum = utf8_width (tags); + minimum = maximum = utf8_width(tags); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnTags::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - auto all = task.getTags (); - if (all.size() > 0) - { - if (_style == "default" || - _style == "list") - { - if (all.size () > 1) - { - std::sort (all.begin (), all.end ()); - auto tags = join (" ", all); +void ColumnTags::render(std::vector& lines, Task& task, int width, Color& color) { + auto all = task.getTags(); + if (all.size() > 0) { + if (_style == "default" || _style == "list") { + if (all.size() > 1) { + std::sort(all.begin(), all.end()); + auto tags = join(" ", all); - all.clear (); - wrapText (all, tags, width, _hyphenate); + all.clear(); + wrapText(all, tags, width, _hyphenate); - for (const auto& i : all) - renderStringLeft (lines, width, color, i); - } - else - renderStringLeft (lines, width, color, all[0]); - } - else if (_style == "indicator") - { - renderStringRight (lines, width, color, Context::getContext ().config.get ("tag.indicator")); - } - else if (_style == "count") - { - renderStringRight (lines, width, color, '[' + format (static_cast (all.size ())) + ']'); + for (const auto& i : all) renderStringLeft(lines, width, color, i); + } else + renderStringLeft(lines, width, color, all[0]); + } else if (_style == "indicator") { + renderStringRight(lines, width, color, Context::getContext().config.get("tag.indicator")); + } else if (_style == "count") { + renderStringRight(lines, width, color, '[' + format(static_cast(all.size())) + ']'); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnTags::modify (Task& task, const std::string& value) -{ +void ColumnTags::modify(Task& task, const std::string& value) { std::string label = " MODIFICATION "; std::string commasep; - std::vector tags; + std::vector tags; // If it's a DOM ref, eval it first. - Lexer lexer (value); + Lexer lexer(value); std::string domRef; Lexer::Type type; - if (lexer.token (domRef, type) && - type == Lexer::Type::dom) - { + if (lexer.token(domRef, type) && type == Lexer::Type::dom) { Eval e; - e.addSource (domSource); + e.addSource(domSource); Variant v; - e.evaluateInfixExpression (value, v); - commasep = (std::string) v; + e.evaluateInfixExpression(value, v); + commasep = (std::string)v; } else { - commasep = (std::string) value; + commasep = (std::string)value; } - for (auto& tag : split (commasep, ',')) - { - tags.push_back ((std::string) tag); - Context::getContext ().debug (label + "tags <-- '" + tag + '\''); + for (auto& tag : split(commasep, ',')) { + tags.push_back((std::string)tag); + Context::getContext().debug(label + "tags <-- '" + tag + '\''); - feedback_special_tags (task, tag); + feedback_special_tags(task, tag); } task.setTags(tags); diff --git a/src/columns/ColTags.h b/src/columns/ColTags.h index 481b01bd7..86d58e335 100644 --- a/src/columns/ColTags.h +++ b/src/columns/ColTags.h @@ -29,16 +29,15 @@ #include -class ColumnTags : public ColumnTypeString -{ -public: - ColumnTags (); - void setStyle (const std::string&); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); - void modify (Task&, const std::string&); +class ColumnTags : public ColumnTypeString { + public: + ColumnTags(); + void setStyle(const std::string&); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); + void modify(Task&, const std::string&); -private: + private: bool _hyphenate; }; diff --git a/src/columns/ColTemplate.cpp b/src/columns/ColTemplate.cpp index 055df7e6e..02f536b1c 100644 --- a/src/columns/ColTemplate.cpp +++ b/src/columns/ColTemplate.cpp @@ -25,49 +25,43 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnTemplate::ColumnTemplate () -{ - _name = "template"; - _style = "long"; - _label = "Template task"; +ColumnTemplate::ColumnTemplate() { + _name = "template"; + _style = "long"; + _label = "Template task"; _modifiable = false; - _styles = {"long", "short"}; - _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; + _styles = {"long", "short"}; + _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnTemplate::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnTemplate::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default" || _style == "long") minimum = maximum = 36; - else if (_style == "short") minimum = maximum = 8; + if (task.has(_name)) { + if (_style == "default" || _style == "long") + minimum = maximum = 36; + else if (_style == "short") + minimum = maximum = 8; } } //////////////////////////////////////////////////////////////////////////////// -void ColumnTemplate::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { +void ColumnTemplate::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { // f30cb9c3-3fc0-483f-bfb2-3bf134f00694 default // f30cb9c3 short - if (_style == "default" || - _style == "long") - renderStringLeft (lines, width, color, task.get(_name)); + if (_style == "default" || _style == "long") + renderStringLeft(lines, width, color, task.get(_name)); else if (_style == "short") - renderStringLeft (lines, width, color, task.get (_name).substr (0, 8)); + renderStringLeft(lines, width, color, task.get(_name).substr(0, 8)); } } diff --git a/src/columns/ColTemplate.h b/src/columns/ColTemplate.h index 88c4f88b6..991f42a37 100644 --- a/src/columns/ColTemplate.h +++ b/src/columns/ColTemplate.h @@ -29,14 +29,13 @@ #include -class ColumnTemplate : public ColumnTypeString -{ -public: - ColumnTemplate (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnTemplate : public ColumnTypeString { + public: + ColumnTemplate(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColTypeDate.cpp b/src/columns/ColTypeDate.cpp index 310606c29..cb459cd3b 100644 --- a/src/columns/ColTypeDate.cpp +++ b/src/columns/ColTypeDate.cpp @@ -25,220 +25,176 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include #include #include -#include #include +#include #include //////////////////////////////////////////////////////////////////////////////// -ColumnTypeDate::ColumnTypeDate () -{ - _name = ""; - _type = "date"; - _style = "formatted"; - _label = ""; - _styles = {"formatted", - "julian", - "epoch", - "iso", - "age", - "relative", - "remaining", - "countdown"}; +ColumnTypeDate::ColumnTypeDate() { + _name = ""; + _type = "date"; + _style = "formatted"; + _label = ""; + _styles = {"formatted", "julian", "epoch", "iso", "age", "relative", "remaining", "countdown"}; Datetime now; - now -= 125; // So that "age" is non-zero. - _examples = {now.toString (Context::getContext ().config.get ("dateformat")), - format (now.toJulian (), 13, 12), - now.toEpochString (), - now.toISO (), - Duration (Datetime () - now).formatVague (true), - '-' + Duration (Datetime () - now).formatVague (true), + now -= 125; // So that "age" is non-zero. + _examples = {now.toString(Context::getContext().config.get("dateformat")), + format(now.toJulian(), 13, 12), + now.toEpochString(), + now.toISO(), + Duration(Datetime() - now).formatVague(true), + '-' + Duration(Datetime() - now).formatVague(true), "", - Duration (Datetime () - now).format ()}; + Duration(Datetime() - now).format()}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnTypeDate::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnTypeDate::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - Datetime date (task.get_date (_name)); + if (task.has(_name)) { + Datetime date(task.get_date(_name)); - if (_style == "default" || - _style == "formatted") - { + if (_style == "default" || _style == "formatted") { // Determine the output date format, which uses a hierarchy of definitions. // rc.report..dateformat // rc.dateformat.report // rc.dateformat. - std::string format = Context::getContext ().config.get ("report." + _report + ".dateformat"); - if (format == "") - format = Context::getContext ().config.get ("dateformat.report"); - if (format == "") - format = Context::getContext ().config.get ("dateformat"); + std::string format = Context::getContext().config.get("report." + _report + ".dateformat"); + if (format == "") format = Context::getContext().config.get("dateformat.report"); + if (format == "") format = Context::getContext().config.get("dateformat"); - minimum = maximum = Datetime::length (format); - } - else if (_style == "countdown") - { + minimum = maximum = Datetime::length(format); + } else if (_style == "countdown") { Datetime now; - minimum = maximum = Duration (date - now).formatVague (true).length (); - } - else if (_style == "julian") - { - minimum = maximum = format (date.toJulian (), 13, 12).length (); - } - else if (_style == "epoch") - { - minimum = maximum = date.toEpochString ().length (); - } - else if (_style == "iso") - { - minimum = maximum = date.toISO ().length (); - } - else if (_style == "age") - { + minimum = maximum = Duration(date - now).formatVague(true).length(); + } else if (_style == "julian") { + minimum = maximum = format(date.toJulian(), 13, 12).length(); + } else if (_style == "epoch") { + minimum = maximum = date.toEpochString().length(); + } else if (_style == "iso") { + minimum = maximum = date.toISO().length(); + } else if (_style == "age") { Datetime now; if (now > date) - minimum = maximum = Duration (now - date).formatVague (true).length (); + minimum = maximum = Duration(now - date).formatVague(true).length(); else - minimum = maximum = Duration (date - now).formatVague (true).length () + 1; - } - else if (_style == "relative") - { + minimum = maximum = Duration(date - now).formatVague(true).length() + 1; + } else if (_style == "relative") { Datetime now; if (now < date) - minimum = maximum = Duration (date - now).formatVague (true).length (); + minimum = maximum = Duration(date - now).formatVague(true).length(); else - minimum = maximum = Duration (now - date).formatVague (true).length () + 1; - } - else if (_style == "remaining") - { + minimum = maximum = Duration(now - date).formatVague(true).length() + 1; + } else if (_style == "remaining") { Datetime now; - if (date > now) - minimum = maximum = Duration (date - now).formatVague (true).length (); + if (date > now) minimum = maximum = Duration(date - now).formatVague(true).length(); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnTypeDate::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - Datetime date (task.get_date (_name)); +void ColumnTypeDate::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + Datetime date(task.get_date(_name)); - if (_style == "default" || - _style == "formatted") - { + if (_style == "default" || _style == "formatted") { // Determine the output date format, which uses a hierarchy of definitions. // rc.report..dateformat // rc.dateformat.report // rc.dateformat - std::string format = Context::getContext ().config.get ("report." + _report + ".dateformat"); - if (format == "") - { - format = Context::getContext ().config.get ("dateformat.report"); - if (format == "") - format = Context::getContext ().config.get ("dateformat"); + std::string format = Context::getContext().config.get("report." + _report + ".dateformat"); + if (format == "") { + format = Context::getContext().config.get("dateformat.report"); + if (format == "") format = Context::getContext().config.get("dateformat"); } - renderStringLeft (lines, width, color, date.toString (format)); - } - else if (_style == "countdown") - { + renderStringLeft(lines, width, color, date.toString(format)); + } else if (_style == "countdown") { Datetime now; - renderStringRight (lines, width, color, Duration (date - now).formatVague (true)); - } - else if (_style == "julian") - renderStringRight (lines, width, color, format (date.toJulian (), 13, 12)); + renderStringRight(lines, width, color, Duration(date - now).formatVague(true)); + } else if (_style == "julian") + renderStringRight(lines, width, color, format(date.toJulian(), 13, 12)); else if (_style == "epoch") - renderStringRight (lines, width, color, date.toEpochString ()); + renderStringRight(lines, width, color, date.toEpochString()); else if (_style == "iso") - renderStringLeft (lines, width, color, date.toISO ()); + renderStringLeft(lines, width, color, date.toISO()); - else if (_style == "age") - { + else if (_style == "age") { Datetime now; if (now > date) - renderStringRight (lines, width, color, Duration (now - date).formatVague (true)); + renderStringRight(lines, width, color, Duration(now - date).formatVague(true)); else - renderStringRight (lines, width, color, '-' + Duration (date - now).formatVague (true)); - } - else if (_style == "relative") - { + renderStringRight(lines, width, color, '-' + Duration(date - now).formatVague(true)); + } else if (_style == "relative") { Datetime now; if (now < date) - renderStringRight (lines, width, color, Duration (date - now).formatVague (true)); + renderStringRight(lines, width, color, Duration(date - now).formatVague(true)); else - renderStringRight (lines, width, color, '-' + Duration (now - date).formatVague (true)); + renderStringRight(lines, width, color, '-' + Duration(now - date).formatVague(true)); } - else if (_style == "remaining") - { + else if (_style == "remaining") { Datetime now; if (date > now) - renderStringRight (lines, width, color, Duration (date - now).formatVague (true)); + renderStringRight(lines, width, color, Duration(date - now).formatVague(true)); } } } //////////////////////////////////////////////////////////////////////////////// -bool ColumnTypeDate::validate (const std::string& input) const -{ - return input.length () ? true : false; +bool ColumnTypeDate::validate(const std::string& input) const { + return input.length() ? true : false; } //////////////////////////////////////////////////////////////////////////////// -void ColumnTypeDate::modify (Task& task, const std::string& value) -{ +void ColumnTypeDate::modify(Task& task, const std::string& value) { // Try to evaluate 'value'. It might work. Variant evaluatedValue; - try - { + try { Eval e; - e.addSource (domSource); - e.evaluateInfixExpression (value, evaluatedValue); + e.addSource(domSource); + e.evaluateInfixExpression(value, evaluatedValue); } - catch (...) - { - evaluatedValue = Variant (value); + catch (...) { + evaluatedValue = Variant(value); } // If v is duration, we need to convert it to date (and implicitly add now), // else store as date. std::string label = " MODIFICATION "; - if (evaluatedValue.type () == Variant::type_duration) - { - Context::getContext ().debug (label + _name + " <-- '" + format ("{1}", format (evaluatedValue.get_duration ())) + "' <-- '" + (std::string) evaluatedValue + "' <-- '" + value + '\''); - evaluatedValue.cast (Variant::type_date); - } - else - { - evaluatedValue.cast (Variant::type_date); - Context::getContext ().debug (label + _name + " <-- '" + format ("{1}", evaluatedValue.get_date ()) + "' <-- '" + (std::string) evaluatedValue + "' <-- '" + value + '\''); + if (evaluatedValue.type() == Variant::type_duration) { + Context::getContext().debug(label + _name + " <-- '" + + format("{1}", format(evaluatedValue.get_duration())) + "' <-- '" + + (std::string)evaluatedValue + "' <-- '" + value + '\''); + evaluatedValue.cast(Variant::type_date); + } else { + evaluatedValue.cast(Variant::type_date); + Context::getContext().debug(label + _name + " <-- '" + + format("{1}", evaluatedValue.get_date()) + "' <-- '" + + (std::string)evaluatedValue + "' <-- '" + value + '\''); } // If a date doesn't parse (2/29/2014) then it evaluates to zero. - if (value != "" && - evaluatedValue.get_date () == 0) - throw format ("'{1}' is not a valid date in the '{2}' format.", value, Variant::dateFormat); + if (value != "" && evaluatedValue.get_date() == 0) + throw format("'{1}' is not a valid date in the '{2}' format.", value, Variant::dateFormat); - task.set (_name, evaluatedValue.get_date ()); + time_t epoch = evaluatedValue.get_date(); + if (epoch < EPOCH_MIN_VALUE || epoch >= EPOCH_MAX_VALUE) { + throw format("'{1}' is not a valid date.", value); + } + task.set(_name, epoch); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColTypeDate.h b/src/columns/ColTypeDate.h index 5d5a9f7b9..dc4bc9aea 100644 --- a/src/columns/ColTypeDate.h +++ b/src/columns/ColTypeDate.h @@ -27,20 +27,20 @@ #ifndef INCLUDED_COLTYPEDATE #define INCLUDED_COLTYPEDATE -#include -#include -#include #include +#include #include -class ColumnTypeDate : public Column -{ -public: - ColumnTypeDate (); - virtual void measure (Task&, unsigned int&, unsigned int&); - virtual void render (std::vector &, Task&, int, Color&); - virtual bool validate (const std::string&) const; - virtual void modify (Task&, const std::string&); +#include +#include + +class ColumnTypeDate : public Column { + public: + ColumnTypeDate(); + virtual void measure(Task&, unsigned int&, unsigned int&); + virtual void render(std::vector&, Task&, int, Color&); + virtual bool validate(const std::string&) const; + virtual void modify(Task&, const std::string&); }; #endif diff --git a/src/columns/ColTypeDuration.cpp b/src/columns/ColTypeDuration.cpp index 942d69b16..ea8511475 100644 --- a/src/columns/ColTypeDuration.cpp +++ b/src/columns/ColTypeDuration.cpp @@ -25,53 +25,45 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include -#include #include +#include #include //////////////////////////////////////////////////////////////////////////////// -ColumnTypeDuration::ColumnTypeDuration () -{ - _type = "duration"; +ColumnTypeDuration::ColumnTypeDuration() { _type = "duration"; } + +//////////////////////////////////////////////////////////////////////////////// +bool ColumnTypeDuration::validate(const std::string& input) const { + return input.length() ? true : false; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnTypeDuration::validate (const std::string& input) const -{ - return input.length () ? true : false; -} - -//////////////////////////////////////////////////////////////////////////////// -void ColumnTypeDuration::modify (Task& task, const std::string& value) -{ +void ColumnTypeDuration::modify(Task& task, const std::string& value) { // Try to evaluate 'value'. It might work. Variant evaluatedValue; - try - { + try { Eval e; - e.addSource (domSource); - e.evaluateInfixExpression (value, evaluatedValue); + e.addSource(domSource); + e.evaluateInfixExpression(value, evaluatedValue); } - catch (...) - { - evaluatedValue = Variant (value); + catch (...) { + evaluatedValue = Variant(value); } - // The duration is stored in raw form, but it must still be valid, - // and therefore is parsed first. + // The duration is first parsed, then stored inside the variant as a `time_t` std::string label = " MODIFICATION "; - if (evaluatedValue.type () == Variant::type_duration) - { - // Store the raw value, for 'recur'. - Context::getContext ().debug (label + _name + " <-- " + (std::string) evaluatedValue + " <-- '" + value + '\''); - task.set (_name, evaluatedValue); - } - else - throw format ("The duration value '{1}' is not supported.", value); + if (evaluatedValue.type() == Variant::type_duration) { + Context::getContext().debug(label + _name + " <-- " + (std::string)evaluatedValue + " <-- '" + + value + '\''); + task.set(_name, evaluatedValue); + } else + throw format("The duration value '{1}' is not supported.", value); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColTypeDuration.h b/src/columns/ColTypeDuration.h index 6ad3b1753..3d2f9544b 100644 --- a/src/columns/ColTypeDuration.h +++ b/src/columns/ColTypeDuration.h @@ -27,16 +27,16 @@ #ifndef INCLUDED_COLTYPEDURATION #define INCLUDED_COLTYPEDURATION -#include #include #include -class ColumnTypeDuration : public Column -{ -public: - ColumnTypeDuration (); - virtual bool validate (const std::string&) const; - virtual void modify (Task&, const std::string&); +#include + +class ColumnTypeDuration : public Column { + public: + ColumnTypeDuration(); + virtual bool validate(const std::string&) const; + virtual void modify(Task&, const std::string&); }; #endif diff --git a/src/columns/ColTypeNumeric.cpp b/src/columns/ColTypeNumeric.cpp index 737e991e8..52cc753a7 100644 --- a/src/columns/ColTypeNumeric.cpp +++ b/src/columns/ColTypeNumeric.cpp @@ -25,48 +25,43 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include -#include #include +#include #include //////////////////////////////////////////////////////////////////////////////// -ColumnTypeNumeric::ColumnTypeNumeric () -{ - _type = "numeric"; +ColumnTypeNumeric::ColumnTypeNumeric() { _type = "numeric"; } + +//////////////////////////////////////////////////////////////////////////////// +bool ColumnTypeNumeric::validate(const std::string& input) const { + return input.length() ? true : false; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnTypeNumeric::validate (const std::string& input) const -{ - return input.length () ? true : false; -} - -//////////////////////////////////////////////////////////////////////////////// -void ColumnTypeNumeric::modify (Task& task, const std::string& value) -{ +void ColumnTypeNumeric::modify(Task& task, const std::string& value) { // Try to evaluate 'value'. It might work. Variant evaluatedValue; - try - { + try { Eval e; - e.addSource (domSource); - e.evaluateInfixExpression (value, evaluatedValue); + e.addSource(domSource); + e.evaluateInfixExpression(value, evaluatedValue); } - catch (...) - { - evaluatedValue = Variant (value); + catch (...) { + evaluatedValue = Variant(value); } std::string label = " MODIFICATION "; - Context::getContext ().debug (label + _name + " <-- '" + evaluatedValue.get_string () + "' <-- '" + value + '\''); + Context::getContext().debug(label + _name + " <-- '" + evaluatedValue.get_string() + "' <-- '" + + value + '\''); // Convert the value of the expression to the correct type if needed - switch (evaluatedValue.type ()) - { + switch (evaluatedValue.type()) { // Expected variants - no conversion case Variant::type_integer: case Variant::type_real: @@ -74,16 +69,16 @@ void ColumnTypeNumeric::modify (Task& task, const std::string& value) // Convertible variants - convert to int case Variant::type_date: case Variant::type_duration: - evaluatedValue.cast (Variant::type_integer); + evaluatedValue.cast(Variant::type_integer); break; // Non-convertible variants case Variant::type_string: - throw format ("The value '{1}' is not a valid numeric value.", evaluatedValue.get_string ()); + throw format("The value '{1}' is not a valid numeric value.", evaluatedValue.get_string()); default: - throw format ("Unexpected variant type: '{1}'", evaluatedValue.type ()); + throw format("Unexpected variant type: '{1}'", evaluatedValue.type()); } - task.set (_name, evaluatedValue); + task.set(_name, evaluatedValue); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColTypeNumeric.h b/src/columns/ColTypeNumeric.h index e530d51f2..edd6508e4 100644 --- a/src/columns/ColTypeNumeric.h +++ b/src/columns/ColTypeNumeric.h @@ -27,16 +27,16 @@ #ifndef INCLUDED_COLTYPENUMERIC #define INCLUDED_COLTYPENUMERIC -#include #include #include -class ColumnTypeNumeric : public Column -{ -public: - ColumnTypeNumeric (); - virtual bool validate (const std::string&) const; - virtual void modify (Task&, const std::string&); +#include + +class ColumnTypeNumeric : public Column { + public: + ColumnTypeNumeric(); + virtual bool validate(const std::string&) const; + virtual void modify(Task&, const std::string&); }; #endif diff --git a/src/columns/ColTypeString.cpp b/src/columns/ColTypeString.cpp index 2d93ca9fc..538e9057f 100644 --- a/src/columns/ColTypeString.cpp +++ b/src/columns/ColTypeString.cpp @@ -25,67 +25,56 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include -#include #include +#include #include -#define STRING_INVALID_MOD "The '{1}' attribute does not allow a value of '{2}'." +#define STRING_INVALID_MOD "The '{1}' attribute does not allow a value of '{2}'." //////////////////////////////////////////////////////////////////////////////// -ColumnTypeString::ColumnTypeString () -{ - _type = "string"; +ColumnTypeString::ColumnTypeString() { _type = "string"; } + +//////////////////////////////////////////////////////////////////////////////// +bool ColumnTypeString::validate(const std::string& input) const { + return input.length() ? true : false; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnTypeString::validate (const std::string& input) const -{ - return input.length () ? true : false; -} - -//////////////////////////////////////////////////////////////////////////////// -void ColumnTypeString::modify (Task& task, const std::string& value) -{ +void ColumnTypeString::modify(Task& task, const std::string& value) { std::string label = " MODIFICATION "; // Only if it's a DOM ref, eval it first. - Lexer lexer (value); + Lexer lexer(value); std::string domRef; Lexer::Type type; - if (lexer.token (domRef, type) && - type == Lexer::Type::dom && + if (lexer.token(domRef, type) && type == Lexer::Type::dom && // Ensure 'value' contains only the DOM reference and no other tokens // The lexer.token returns false for end-of-string. // This works as long as all the DOM references we should support consist // only of a single token. - lexer.token (domRef, type) == false) - { + lexer.token(domRef, type) == false) { Eval e; - e.addSource (domSource); + e.addSource(domSource); Variant v; - e.evaluateInfixExpression (value, v); - std::string strValue = (std::string) v; - if (validate (strValue)) - { - task.set (_name, strValue); - Context::getContext ().debug (label + _name + " <-- '" + strValue + "' <-- '" + value + '\''); - } - else - throw format (STRING_INVALID_MOD, _name, value); - } - else - { - if (validate (value)) - { - task.set (_name, value); - Context::getContext ().debug (label + _name + " <-- '" + value + '\''); - } - else - throw format (STRING_INVALID_MOD, _name, value); + e.evaluateInfixExpression(value, v); + std::string strValue = (std::string)v; + if (validate(strValue)) { + task.set(_name, strValue); + Context::getContext().debug(label + _name + " <-- '" + strValue + "' <-- '" + value + '\''); + } else + throw format(STRING_INVALID_MOD, _name, value); + } else { + if (validate(value)) { + task.set(_name, value); + Context::getContext().debug(label + _name + " <-- '" + value + '\''); + } else + throw format(STRING_INVALID_MOD, _name, value); } } diff --git a/src/columns/ColTypeString.h b/src/columns/ColTypeString.h index 4bf5477fa..691a6916e 100644 --- a/src/columns/ColTypeString.h +++ b/src/columns/ColTypeString.h @@ -27,16 +27,16 @@ #ifndef INCLUDED_COLTYPESTRING #define INCLUDED_COLTYPESTRING -#include #include #include -class ColumnTypeString : public Column -{ -public: - ColumnTypeString (); - virtual bool validate (const std::string&) const; - virtual void modify (Task&, const std::string&); +#include + +class ColumnTypeString : public Column { + public: + ColumnTypeString(); + virtual bool validate(const std::string&) const; + virtual void modify(Task&, const std::string&); }; #endif diff --git a/src/columns/ColUDA.cpp b/src/columns/ColUDA.cpp index 57dd628bb..a6452c631 100644 --- a/src/columns/ColUDA.cpp +++ b/src/columns/ColUDA.cpp @@ -25,38 +25,36 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include #include -#include #include -#include +#include #include +#include //////////////////////////////////////////////////////////////////////////////// -ColumnUDAString::ColumnUDAString () -{ - _name = ""; // Gets overwritten at runtime. - _style = "default"; - _label = ""; +ColumnUDAString::ColumnUDAString() { + _name = ""; // Gets overwritten at runtime. + _style = "default"; + _label = ""; _modifiable = true; - _uda = true; - _hyphenate = true; - _styles = {_style, "indicator"}; + _uda = true; + _hyphenate = true; + _styles = {_style, "indicator"}; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnUDAString::validate (const std::string& value) const -{ +bool ColumnUDAString::validate(const std::string& value) const { // No restrictions. - if (_values.size () == 0) - return true; + if (_values.size() == 0) return true; // Look for exact match value. for (auto& i : _values) - if (i == value) - return true; + if (i == value) return true; // Fail if not found. return false; @@ -65,83 +63,61 @@ bool ColumnUDAString::validate (const std::string& value) const //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. // -void ColumnUDAString::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUDAString::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default") - { - std::string value = task.get (_name); - if (value != "") - { - auto stripped = Color::strip (value); - maximum = longestLine (stripped); - minimum = longestWord (stripped); + if (task.has(_name)) { + if (_style == "default") { + std::string value = task.get(_name); + if (value != "") { + auto stripped = Color::strip(value); + maximum = longestLine(stripped); + minimum = longestWord(stripped); } - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - minimum = maximum = utf8_width (indicator); + minimum = maximum = utf8_width(indicator); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnUDAString::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "default") - { - std::string value = task.get (_name); - std::vector raw; - wrapText (raw, value, width, _hyphenate); +void ColumnUDAString::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + if (_style == "default") { + std::string value = task.get(_name); + std::vector raw; + wrapText(raw, value, width, _hyphenate); - for (auto& i : raw) - renderStringLeft (lines, width, color, i); - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + for (auto& i : raw) renderStringLeft(lines, width, color, i); + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - renderStringRight (lines, width, color, indicator); + renderStringRight(lines, width, color, indicator); } } } //////////////////////////////////////////////////////////////////////////////// -ColumnUDANumeric::ColumnUDANumeric () -{ - _name = ""; - _type = "numeric"; - _style = "default"; - _label = ""; - _uda = true; - _styles = {_style, "indicator"}; +ColumnUDANumeric::ColumnUDANumeric() { + _name = ""; + _type = "numeric"; + _style = "default"; + _label = ""; + _uda = true; + _styles = {_style, "indicator"}; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnUDANumeric::validate (const std::string& value) const -{ +bool ColumnUDANumeric::validate(const std::string& value) const { // No restrictions. - if (_values.size () == 0) - return true; + if (_values.size() == 0) return true; // Look for exact match value. for (auto& i : _values) - if (i == value) - return true; + if (i == value) return true; // Fail if not found. return false; @@ -150,75 +126,55 @@ bool ColumnUDANumeric::validate (const std::string& value) const //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. // -void ColumnUDANumeric::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUDANumeric::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); - if (value != "") - minimum = maximum = value.length (); - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); + if (value != "") minimum = maximum = value.length(); + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - minimum = maximum = utf8_width (indicator); + minimum = maximum = utf8_width(indicator); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnUDANumeric::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); - renderStringRight (lines, width, color, value); - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; +void ColumnUDANumeric::render(std::vector& lines, Task& task, int width, + Color& color) { + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); + renderStringRight(lines, width, color, value); + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - renderStringRight (lines, width, color, indicator); + renderStringRight(lines, width, color, indicator); } } } //////////////////////////////////////////////////////////////////////////////// -ColumnUDADate::ColumnUDADate () -{ - _name = ""; - _type = "date"; - _style = "default"; - _label = ""; - _uda = true; - _styles = {_style, "indicator"}; +ColumnUDADate::ColumnUDADate() { + _name = ""; + _type = "date"; + _style = "default"; + _label = ""; + _uda = true; + _styles = {_style, "indicator"}; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnUDADate::validate (const std::string& value) const -{ +bool ColumnUDADate::validate(const std::string& value) const { // No restrictions. - if (_values.size () == 0) - return true; + if (_values.size() == 0) return true; // Look for exact match value. for (auto& i : _values) - if (i == value) - return true; + if (i == value) return true; // Fail if not found. return false; @@ -227,101 +183,77 @@ bool ColumnUDADate::validate (const std::string& value) const //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. // -void ColumnUDADate::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUDADate::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); - if (value != "") - { + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); + if (value != "") { // Determine the output date format, which uses a hierarchy of definitions. // rc.report..dateformat // rc.dateformat.report // rc.dateformat - Datetime date (strtoll (value.c_str (), nullptr, 10)); - auto format = Context::getContext ().config.get ("report." + _report + ".dateformat"); - if (format == "") - format = Context::getContext ().config.get ("dateformat.report"); - if (format == "") - format = Context::getContext ().config.get ("dateformat"); + Datetime date(strtoll(value.c_str(), nullptr, 10)); + auto format = Context::getContext().config.get("report." + _report + ".dateformat"); + if (format == "") format = Context::getContext().config.get("dateformat.report"); + if (format == "") format = Context::getContext().config.get("dateformat"); - minimum = maximum = Datetime::length (format); + minimum = maximum = Datetime::length(format); } - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - minimum = maximum = utf8_width (indicator); + minimum = maximum = utf8_width(indicator); } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnUDADate::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); +void ColumnUDADate::render(std::vector& lines, Task& task, int width, Color& color) { + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); // Determine the output date format, which uses a hierarchy of definitions. // rc.report..dateformat // rc.dateformat.report // rc.dateformat. - auto format = Context::getContext ().config.get ("report." + _report + ".dateformat"); - if (format == "") - { - format = Context::getContext ().config.get ("dateformat.report"); - if (format == "") - format = Context::getContext ().config.get ("dateformat"); + auto format = Context::getContext().config.get("report." + _report + ".dateformat"); + if (format == "") { + format = Context::getContext().config.get("dateformat.report"); + if (format == "") format = Context::getContext().config.get("dateformat"); } - renderStringLeft (lines, width, color, Datetime (strtoll (value.c_str (), nullptr, 10)).toString (format)); - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + renderStringLeft(lines, width, color, + Datetime(strtoll(value.c_str(), nullptr, 10)).toString(format)); + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - renderStringRight (lines, width, color, indicator); + renderStringRight(lines, width, color, indicator); } } } //////////////////////////////////////////////////////////////////////////////// -ColumnUDADuration::ColumnUDADuration () -{ - _name = ""; - _type = "duration"; - _style = "default"; - _label = ""; - _uda = true; - _styles = {_style, "indicator"}; +ColumnUDADuration::ColumnUDADuration() { + _name = ""; + _type = "duration"; + _style = "default"; + _label = ""; + _uda = true; + _styles = {_style, "indicator"}; } //////////////////////////////////////////////////////////////////////////////// -bool ColumnUDADuration::validate (const std::string& value) const -{ +bool ColumnUDADuration::validate(const std::string& value) const { // No restrictions. - if (_values.size () == 0) - return true; + if (_values.size() == 0) return true; // Look for exact match value. for (auto& i : _values) - if (i == value) - return true; + if (i == value) return true; // Fail if not found. return false; @@ -330,56 +262,58 @@ bool ColumnUDADuration::validate (const std::string& value) const //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. // -void ColumnUDADuration::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUDADuration::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { minimum = maximum = 0; - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); - if (value != "") - minimum = maximum = Duration (value).formatISO ().length (); - } - else if (_style == "indicator") - { - if (task.has (_name)) - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); + if (value != "") minimum = maximum = Duration(value).formatISO().length(); + } else if (_style == "indicator") { + if (task.has(_name)) { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - minimum = maximum = utf8_width (indicator); - } - else + minimum = maximum = utf8_width(indicator); + } else minimum = maximum = 0; } } } //////////////////////////////////////////////////////////////////////////////// -void ColumnUDADuration::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ - if (task.has (_name)) - { - if (_style == "default") - { - auto value = task.get (_name); - renderStringRight (lines, width, color, Duration (value).formatISO ()); - } - else if (_style == "indicator") - { - auto indicator = Context::getContext ().config.get ("uda." + _name + ".indicator"); - if (indicator == "") - indicator = "U"; +void ColumnUDADuration::render(std::vector& lines, Task& task, int width, + Color& color) { + if (task.has(_name)) { + if (_style == "default") { + auto value = task.get(_name); + renderStringRight(lines, width, color, Duration(value).formatISO()); + } else if (_style == "indicator") { + auto indicator = Context::getContext().config.get("uda." + _name + ".indicator"); + if (indicator == "") indicator = "U"; - renderStringRight (lines, width, color, indicator); + renderStringRight(lines, width, color, indicator); } } } //////////////////////////////////////////////////////////////////////////////// +ColumnUDAUUID::ColumnUDAUUID() { + _name = ""; + _type = "uuid"; + _style = "long"; + _label = ""; + _modifiable = true; + _uda = true; + _styles = {"long", "short"}; + _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; +} + +//////////////////////////////////////////////////////////////////////////////// +bool ColumnUDAUUID::validate(const std::string& input) const { + Lexer lex(input); + std::string token; + Lexer::Type type; + return lex.isUUID(token, type, true); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColUDA.h b/src/columns/ColUDA.h index 6e21802a0..04d57a8d7 100644 --- a/src/columns/ColUDA.h +++ b/src/columns/ColUDA.h @@ -27,64 +27,68 @@ #ifndef INCLUDED_COLUDA #define INCLUDED_COLUDA -#include -#include #include #include +#include +#include +#include //////////////////////////////////////////////////////////////////////////////// -class ColumnUDAString : public ColumnTypeString -{ -public: - ColumnUDAString (); - bool validate (const std::string&) const; - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUDAString : public ColumnTypeString { + public: + ColumnUDAString(); + bool validate(const std::string&) const; + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -public: - std::vector _values; + public: + std::vector _values; -private: + private: bool _hyphenate; }; //////////////////////////////////////////////////////////////////////////////// -class ColumnUDANumeric : public ColumnTypeNumeric -{ -public: - ColumnUDANumeric (); - bool validate (const std::string&) const; - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUDANumeric : public ColumnTypeNumeric { + public: + ColumnUDANumeric(); + bool validate(const std::string&) const; + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -public: - std::vector _values; + public: + std::vector _values; }; //////////////////////////////////////////////////////////////////////////////// -class ColumnUDADate : public ColumnTypeDate -{ -public: - ColumnUDADate (); - bool validate (const std::string&) const; - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUDADate : public ColumnTypeDate { + public: + ColumnUDADate(); + bool validate(const std::string&) const; + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -public: - std::vector _values; + public: + std::vector _values; }; //////////////////////////////////////////////////////////////////////////////// -class ColumnUDADuration : public ColumnTypeDuration -{ -public: - ColumnUDADuration (); - bool validate (const std::string&) const; - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUDADuration : public ColumnTypeDuration { + public: + ColumnUDADuration(); + bool validate(const std::string&) const; + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -public: - std::vector _values; + public: + std::vector _values; +}; + +//////////////////////////////////////////////////////////////////////////////// +class ColumnUDAUUID : public ColumnUUID { + public: + ColumnUDAUUID(); + bool validate(const std::string&) const; }; #endif diff --git a/src/columns/ColUUID.cpp b/src/columns/ColUUID.cpp index 34fbe2a67..3b81a4419 100644 --- a/src/columns/ColUUID.cpp +++ b/src/columns/ColUUID.cpp @@ -25,47 +25,43 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnUUID::ColumnUUID () -{ - _name = "uuid"; - _style = "long"; - _label = "UUID"; +ColumnUUID::ColumnUUID() { + _name = "uuid"; + _style = "long"; + _label = "UUID"; _modifiable = false; - _styles = {"long", "short"}; - _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; + _styles = {"long", "short"}; + _examples = {"f30cb9c3-3fc0-483f-bfb2-3bf134f00694", "f30cb9c3"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnUUID::measure (Task&, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUUID::measure(Task&, unsigned int& minimum, unsigned int& maximum) { // Mandatory attribute, no need to check the value. - if (_style == "default" || _style == "long") minimum = maximum = 36; - else if (_style == "short") minimum = maximum = 8; + if (_style == "default" || _style == "long") + minimum = maximum = 36; + else if (_style == "short") + minimum = maximum = 8; } //////////////////////////////////////////////////////////////////////////////// -void ColumnUUID::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ +void ColumnUUID::render(std::vector& lines, Task& task, int width, Color& color) { // No need to check the presence of UUID - all tasks have one. // f30cb9c3-3fc0-483f-bfb2-3bf134f00694 default // f30cb9c3 short - if (_style == "default" || - _style == "long") - renderStringLeft (lines, width, color, task.get (_name)); + if (_style == "default" || _style == "long") + renderStringLeft(lines, width, color, task.get(_name)); else if (_style == "short") - renderStringLeft (lines, width, color, task.get (_name).substr (0, 8)); + renderStringLeft(lines, width, color, task.get(_name).substr(0, 8)); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColUUID.h b/src/columns/ColUUID.h index 5adadcc56..7a8232a41 100644 --- a/src/columns/ColUUID.h +++ b/src/columns/ColUUID.h @@ -29,14 +29,13 @@ #include -class ColumnUUID : public ColumnTypeString -{ -public: - ColumnUUID (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUUID : public ColumnTypeString { + public: + ColumnUUID(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColUntil.cpp b/src/columns/ColUntil.cpp index d6f74fafe..238c2f2df 100644 --- a/src/columns/ColUntil.cpp +++ b/src/columns/ColUntil.cpp @@ -25,12 +25,13 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnUntil::ColumnUntil () -{ - _name = "until"; +ColumnUntil::ColumnUntil() { + _name = "until"; _label = "Until"; } diff --git a/src/columns/ColUntil.h b/src/columns/ColUntil.h index 0161fda2e..5f180fba6 100644 --- a/src/columns/ColUntil.h +++ b/src/columns/ColUntil.h @@ -29,10 +29,9 @@ #include -class ColumnUntil : public ColumnTypeDate -{ -public: - ColumnUntil (); +class ColumnUntil : public ColumnTypeDate { + public: + ColumnUntil(); }; #endif diff --git a/src/columns/ColUrgency.cpp b/src/columns/ColUrgency.cpp index f1c89720b..d202a7d91 100644 --- a/src/columns/ColUrgency.cpp +++ b/src/columns/ColUrgency.cpp @@ -25,43 +25,38 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include //////////////////////////////////////////////////////////////////////////////// -ColumnUrgency::ColumnUrgency () -{ - _name = "urgency"; - _style = "real"; - _label = "Urgency"; +ColumnUrgency::ColumnUrgency() { + _name = "urgency"; + _style = "real"; + _label = "Urgency"; _modifiable = false; - _styles = {"real", "integer"}; - _examples = {"4.6", "4"}; + _styles = {"real", "integer"}; + _examples = {"4.6", "4"}; } //////////////////////////////////////////////////////////////////////////////// // Set the minimum and maximum widths for the value. -void ColumnUrgency::measure (Task& task, unsigned int& minimum, unsigned int& maximum) -{ +void ColumnUrgency::measure(Task& task, unsigned int& minimum, unsigned int& maximum) { if (_style == "default" || _style == "real") - minimum = maximum = format (task.urgency (), 4, 3).length (); + minimum = maximum = format(task.urgency(), 4, 3).length(); else if (_style == "integer") - minimum = maximum = format ((int)task.urgency ()).length (); + minimum = maximum = format((int)task.urgency()).length(); } //////////////////////////////////////////////////////////////////////////////// -void ColumnUrgency::render ( - std::vector & lines, - Task& task, - int width, - Color& color) -{ +void ColumnUrgency::render(std::vector& lines, Task& task, int width, Color& color) { if (_style == "default" || _style == "real") - renderDouble (lines, width, color, task.urgency ()); + renderDouble(lines, width, color, task.urgency()); else if (_style == "integer") - renderInteger (lines, width, color, static_cast (task.urgency ())); + renderInteger(lines, width, color, static_cast(task.urgency())); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/ColUrgency.h b/src/columns/ColUrgency.h index 918334cd7..c4c31ab0d 100644 --- a/src/columns/ColUrgency.h +++ b/src/columns/ColUrgency.h @@ -29,14 +29,13 @@ #include -class ColumnUrgency : public ColumnTypeNumeric -{ -public: - ColumnUrgency (); - void measure (Task&, unsigned int&, unsigned int&); - void render (std::vector &, Task&, int, Color&); +class ColumnUrgency : public ColumnTypeNumeric { + public: + ColumnUrgency(); + void measure(Task&, unsigned int&, unsigned int&); + void render(std::vector&, Task&, int, Color&); -private: + private: }; #endif diff --git a/src/columns/ColWait.cpp b/src/columns/ColWait.cpp index e0286eadf..1f2c97e83 100644 --- a/src/columns/ColWait.cpp +++ b/src/columns/ColWait.cpp @@ -25,12 +25,13 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include //////////////////////////////////////////////////////////////////////////////// -ColumnWait::ColumnWait () -{ - _name = "wait"; +ColumnWait::ColumnWait() { + _name = "wait"; _label = "Wait"; } diff --git a/src/columns/ColWait.h b/src/columns/ColWait.h index f5a848b25..7c4b40435 100644 --- a/src/columns/ColWait.h +++ b/src/columns/ColWait.h @@ -29,10 +29,9 @@ #include -class ColumnWait : public ColumnTypeDate -{ -public: - ColumnWait (); +class ColumnWait : public ColumnTypeDate { + public: + ColumnWait(); }; #endif diff --git a/src/columns/Column.cpp b/src/columns/Column.cpp index 402b4ca3f..131b48da4 100644 --- a/src/columns/Column.cpp +++ b/src/columns/Column.cpp @@ -25,10 +25,8 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include +// cmake.h include header must come first + #include #include #include @@ -41,294 +39,289 @@ #include #include #include -#include #include +#include #include #include #include #include #include +#include +#include #include #include -#include -#include #include -#include +#include +#include #include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// // Supports the complete column definition: // // [.] // -Column* Column::factory (const std::string& name, const std::string& report) -{ +Column* Column::factory(const std::string& name, const std::string& report) { // Decompose name into type and style. - auto dot = name.find ('.'); + auto dot = name.find('.'); std::string column_name; std::string column_style; - if (dot != std::string::npos) - { - column_name = name.substr (0, dot); - column_style = name.substr (dot + 1); - } - else - { + if (dot != std::string::npos) { + column_name = name.substr(0, dot); + column_style = name.substr(dot + 1); + } else { column_name = name; column_style = "default"; } Column* c; - if (column_name == "depends") c = new ColumnDepends (); - else if (column_name == "description") c = new ColumnDescription (); - else if (column_name == "due") c = new ColumnDue (); - else if (column_name == "end") c = new ColumnEnd (); - else if (column_name == "entry") c = new ColumnEntry (); - else if (column_name == "id") c = new ColumnID (); - else if (column_name == "imask") c = new ColumnIMask (); - else if (column_name == "last") c = new ColumnLast (); - else if (column_name == "mask") c = new ColumnMask (); - else if (column_name == "modified") c = new ColumnModified (); - else if (column_name == "parent") c = new ColumnParent (); - else if (column_name == "project") c = new ColumnProject (); - else if (column_name == "recur") c = new ColumnRecur (); - else if (column_name == "rtype") c = new ColumnRType (); - else if (column_name == "scheduled") c = new ColumnScheduled (); - else if (column_name == "start") c = new ColumnStart (); - else if (column_name == "status") c = new ColumnStatus (); - else if (column_name == "tags") c = new ColumnTags (); - else if (column_name == "template") c = new ColumnTemplate (); - else if (column_name == "until") c = new ColumnUntil (); - else if (column_name == "urgency") c = new ColumnUrgency (); - else if (column_name == "uuid") c = new ColumnUUID (); - else if (column_name == "wait") c = new ColumnWait (); + if (column_name == "depends") + c = new ColumnDepends(); + else if (column_name == "description") + c = new ColumnDescription(); + else if (column_name == "due") + c = new ColumnDue(); + else if (column_name == "end") + c = new ColumnEnd(); + else if (column_name == "entry") + c = new ColumnEntry(); + else if (column_name == "id") + c = new ColumnID(); + else if (column_name == "imask") + c = new ColumnIMask(); + else if (column_name == "last") + c = new ColumnLast(); + else if (column_name == "mask") + c = new ColumnMask(); + else if (column_name == "modified") + c = new ColumnModified(); + else if (column_name == "parent") + c = new ColumnParent(); + else if (column_name == "project") + c = new ColumnProject(); + else if (column_name == "recur") + c = new ColumnRecur(); + else if (column_name == "rtype") + c = new ColumnRType(); + else if (column_name == "scheduled") + c = new ColumnScheduled(); + else if (column_name == "start") + c = new ColumnStart(); + else if (column_name == "status") + c = new ColumnStatus(); + else if (column_name == "tags") + c = new ColumnTags(); + else if (column_name == "template") + c = new ColumnTemplate(); + else if (column_name == "until") + c = new ColumnUntil(); + else if (column_name == "urgency") + c = new ColumnUrgency(); + else if (column_name == "uuid") + c = new ColumnUUID(); + else if (column_name == "wait") + c = new ColumnWait(); // UDA. - else if (Context::getContext ().config.has ("uda." + column_name + ".type")) - c = Column::uda (column_name); + else if (Context::getContext().config.has("uda." + column_name + ".type")) + c = Column::uda(column_name); else - throw format ("Unrecognized column name '{1}'.", column_name); + throw format("Unrecognized column name '{1}'.", column_name); - c->setReport (report); - c->setStyle (column_style); + c->setReport(report); + c->setStyle(column_style); return c; } //////////////////////////////////////////////////////////////////////////////// // Bulk column instantiation. -void Column::factory (std::map & all) -{ +void Column::factory(std::map& all) { Column* c; - c = new ColumnDepends (); all[c->_name] = c; - c = new ColumnDescription (); all[c->_name] = c; - c = new ColumnDue (); all[c->_name] = c; - c = new ColumnEnd (); all[c->_name] = c; - c = new ColumnEntry (); all[c->_name] = c; - c = new ColumnID (); all[c->_name] = c; - c = new ColumnIMask (); all[c->_name] = c; - c = new ColumnLast (); all[c->_name] = c; - c = new ColumnMask (); all[c->_name] = c; - c = new ColumnModified (); all[c->_name] = c; - c = new ColumnParent (); all[c->_name] = c; - c = new ColumnProject (); all[c->_name] = c; - c = new ColumnRecur (); all[c->_name] = c; - c = new ColumnRType (); all[c->_name] = c; - c = new ColumnScheduled (); all[c->_name] = c; - c = new ColumnStart (); all[c->_name] = c; - c = new ColumnStatus (); all[c->_name] = c; - c = new ColumnTags (); all[c->_name] = c; - c = new ColumnTemplate (); all[c->_name] = c; - c = new ColumnUntil (); all[c->_name] = c; - c = new ColumnUrgency (); all[c->_name] = c; - c = new ColumnUUID (); all[c->_name] = c; - c = new ColumnWait (); all[c->_name] = c; + c = new ColumnDepends(); + all[c->_name] = c; + c = new ColumnDescription(); + all[c->_name] = c; + c = new ColumnDue(); + all[c->_name] = c; + c = new ColumnEnd(); + all[c->_name] = c; + c = new ColumnEntry(); + all[c->_name] = c; + c = new ColumnID(); + all[c->_name] = c; + c = new ColumnIMask(); + all[c->_name] = c; + c = new ColumnLast(); + all[c->_name] = c; + c = new ColumnMask(); + all[c->_name] = c; + c = new ColumnModified(); + all[c->_name] = c; + c = new ColumnParent(); + all[c->_name] = c; + c = new ColumnProject(); + all[c->_name] = c; + c = new ColumnRecur(); + all[c->_name] = c; + c = new ColumnRType(); + all[c->_name] = c; + c = new ColumnScheduled(); + all[c->_name] = c; + c = new ColumnStart(); + all[c->_name] = c; + c = new ColumnStatus(); + all[c->_name] = c; + c = new ColumnTags(); + all[c->_name] = c; + c = new ColumnTemplate(); + all[c->_name] = c; + c = new ColumnUntil(); + all[c->_name] = c; + c = new ColumnUrgency(); + all[c->_name] = c; + c = new ColumnUUID(); + all[c->_name] = c; + c = new ColumnWait(); + all[c->_name] = c; - Column::uda (all); + Column::uda(all); } //////////////////////////////////////////////////////////////////////////////// -void Column::uda (std::map & all) -{ +void Column::uda(std::map& all) { // For each UDA, instantiate and initialize ColumnUDA. - std::set udas; + std::set udas; - for (const auto& i : Context::getContext ().config) - { - if (i.first.substr (0, 4) == "uda.") - { - std::string::size_type period = 4; // One byte after the first '.'. + for (const auto& i : Context::getContext().config) { + if (i.first.substr(0, 4) == "uda.") { + std::string::size_type period = 4; // One byte after the first '.'. - if ((period = i.first.find ('.', period)) != std::string::npos) - udas.insert (i.first.substr (4, period - 4)); + if ((period = i.first.find('.', period)) != std::string::npos) + udas.insert(i.first.substr(4, period - 4)); } } - for (const auto& uda : udas) - { - if (all.find (uda) != all.end ()) - throw format ("The UDA named '{1}' is the same as a core attribute, and is not permitted.", uda); + for (const auto& uda : udas) { + if (all.find(uda) != all.end()) + throw format("The UDA named '{1}' is the same as a core attribute, and is not permitted.", + uda); - Column* c = Column::uda (uda); - if (c) - all[c->_name] = c; + Column* c = Column::uda(uda); + if (c) all[c->_name] = c; } } //////////////////////////////////////////////////////////////////////////////// -Column* Column::uda (const std::string& name) -{ - auto type = Context::getContext ().config.get ("uda." + name + ".type"); - auto label = Context::getContext ().config.get ("uda." + name + ".label"); - auto values = Context::getContext ().config.get ("uda." + name + ".values"); +Column* Column::uda(const std::string& name) { + auto type = Context::getContext().config.get("uda." + name + ".type"); + auto label = Context::getContext().config.get("uda." + name + ".label"); + auto values = Context::getContext().config.get("uda." + name + ".values"); - if (type == "string") - { - auto c = new ColumnUDAString (); + if (type == "string") { + auto c = new ColumnUDAString(); c->_name = name; c->_label = label; - if (values != "") - c->_values = split (values, ','); + if (values != "") c->_values = split(values, ','); return c; - } - else if (type == "numeric") - { - auto c = new ColumnUDANumeric (); + } else if (type == "numeric") { + auto c = new ColumnUDANumeric(); c->_name = name; c->_label = label; - if (values != "") - c->_values = split (values, ','); + if (values != "") c->_values = split(values, ','); return c; - } - else if (type == "date") - { - auto c = new ColumnUDADate (); + } else if (type == "date") { + auto c = new ColumnUDADate(); c->_name = name; c->_label = label; - if (values != "") - c->_values = split (values, ','); + if (values != "") c->_values = split(values, ','); return c; - } - else if (type == "duration") - { - auto c = new ColumnUDADuration (); + } else if (type == "duration") { + auto c = new ColumnUDADuration(); c->_name = name; c->_label = label; - if (values != "") - c->_values = split (values, ','); + if (values != "") c->_values = split(values, ','); return c; - } - else if (type != "") - throw std::string ("User defined attributes may only be of type 'string', 'date', 'duration' or 'numeric'."); + } else if (type == "uuid") { + auto c = new ColumnUDAUUID(); + c->_name = name; + c->_label = label; + return c; + } else if (type != "") + throw std::string( + "User defined attributes may only be of type 'string', 'uuid', date', 'duration' or " + "'numeric'."); return nullptr; } //////////////////////////////////////////////////////////////////////////////// -Column::Column () -: _name ("") -, _type ("string") -, _style ("default") -, _label ("") -, _report ("") -, _modifiable (true) -, _uda (false) -, _fixed_width (false) -{ -} +Column::Column() + : _name(""), + _type("string"), + _style("default"), + _label(""), + _report(""), + _modifiable(true), + _uda(false), + _fixed_width(false) {} //////////////////////////////////////////////////////////////////////////////// -void Column::renderHeader ( - std::vector & lines, - int width, - Color& color) -{ - if (Context::getContext ().verbose ("label") && - _label != "") - { +void Column::renderHeader(std::vector& lines, int width, Color& color) { + if (Context::getContext().verbose("label") && _label != "") { // Create a basic label. std::string header; - header.reserve (width); + header.reserve(width); header = _label; // Create a fungible copy. Color c = color; // Now underline the header, or add a dashed line. - if (Context::getContext ().color () && - Context::getContext ().config.getBoolean ("fontunderline")) - { - c.blend (Color (Color::nocolor, Color::nocolor, true, false, false)); - lines.push_back (c.colorize (leftJustify (header, width))); - } - else - { - lines.push_back (c.colorize (leftJustify (header, width))); - lines.push_back (c.colorize (std::string (width, '-'))); + if (Context::getContext().color() && Context::getContext().config.getBoolean("fontunderline")) { + c.blend(Color(Color::nocolor, Color::nocolor, true, false, false)); + lines.push_back(c.colorize(leftJustify(header, width))); + } else { + lines.push_back(c.colorize(leftJustify(header, width))); + lines.push_back(c.colorize(std::string(width, '-'))); } } } //////////////////////////////////////////////////////////////////////////////// -void Column::setStyle (const std::string& style) -{ - if (style != "default" && - std::find (_styles.begin (), _styles.end (), style) == _styles.end ()) - throw format ("Unrecognized column format '{1}.{2}'", _name, style); +void Column::setStyle(const std::string& style) { + if (style != "default" && std::find(_styles.begin(), _styles.end(), style) == _styles.end()) + throw format("Unrecognized column format '{1}.{2}'", _name, style); _style = style; } //////////////////////////////////////////////////////////////////////////////// // All integer values are right-justified. -void Column::renderInteger ( - std::vector & lines, - int width, - Color& color, - int value) -{ - lines.push_back ( - color.colorize ( - rightJustify (value, width))); +void Column::renderInteger(std::vector& lines, int width, Color& color, int value) { + lines.push_back(color.colorize(rightJustify(value, width))); } //////////////////////////////////////////////////////////////////////////////// // All floating point values are right-justified. -void Column::renderDouble ( - std::vector & lines, - int width, - Color& color, - double value) -{ - lines.push_back ( - color.colorize ( - rightJustify ( - format (value, 4, 3), width))); +void Column::renderDouble(std::vector& lines, int width, Color& color, double value) { + lines.push_back(color.colorize(rightJustify(format(value, 4, 3), width))); } //////////////////////////////////////////////////////////////////////////////// -void Column::renderStringLeft ( - std::vector & lines, - int width, - Color& color, - const std::string& value) -{ - lines.push_back ( - color.colorize ( - leftJustify (value, width))); +void Column::renderStringLeft(std::vector& lines, int width, Color& color, + const std::string& value) { + lines.push_back(color.colorize(leftJustify(value, width))); } //////////////////////////////////////////////////////////////////////////////// -void Column::renderStringRight ( - std::vector & lines, - int width, - Color& color, - const std::string& value) -{ - lines.push_back ( - color.colorize ( - rightJustify (value, width))); +void Column::renderStringRight(std::vector& lines, int width, Color& color, + const std::string& value) { + lines.push_back(color.colorize(rightJustify(value, width))); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/columns/Column.h b/src/columns/Column.h index 45cf0f613..20dad26fb 100644 --- a/src/columns/Column.h +++ b/src/columns/Column.h @@ -27,51 +27,51 @@ #ifndef INCLUDED_COLUMN #define INCLUDED_COLUMN -#include -#include #include #include -class Column -{ -public: - static Column* factory (const std::string&, const std::string&); - static void factory (std::map &); - static void uda (std::map &); - static Column* uda (const std::string&); +#include +#include - Column (); - virtual ~Column () = default; +class Column { + public: + static Column* factory(const std::string&, const std::string&); + static void factory(std::map&); + static void uda(std::map&); + static Column* uda(const std::string&); - const std::string& name () const { return _name; } - const std::string& style () const { return _style; } - const std::string& label () const { return _label; } - const std::string& type () const { return _type; } - bool modifiable () const { return _modifiable; } - bool is_uda () const { return _uda; } - bool is_fixed_width () const { return _fixed_width; } - std::vector styles () const { return _styles; } - std::vector examples () const { return _examples; } + Column(); + virtual ~Column() = default; - virtual void setStyle (const std::string&); - virtual void setLabel (const std::string& value) { _label = value; } - virtual void setReport (const std::string& value) { _report = value; } + const std::string& name() const { return _name; } + const std::string& style() const { return _style; } + const std::string& label() const { return _label; } + const std::string& type() const { return _type; } + bool modifiable() const { return _modifiable; } + bool is_uda() const { return _uda; } + bool is_fixed_width() const { return _fixed_width; } + std::vector styles() const { return _styles; } + std::vector examples() const { return _examples; } - virtual void measure (const std::string&, unsigned int&, unsigned int&) {}; - virtual void measure (Task&, unsigned int&, unsigned int&) {}; - virtual void renderHeader (std::vector &, int, Color&); - virtual void render (std::vector &, const std::string&, int, Color&) {}; - virtual void render (std::vector &, Task&, int, Color&) {}; - virtual bool validate (const std::string&) const {return false;}; - virtual void modify (Task&, const std::string&) {}; + virtual void setStyle(const std::string&); + virtual void setLabel(const std::string& value) { _label = value; } + virtual void setReport(const std::string& value) { _report = value; } -protected: - void renderInteger (std::vector &, int, Color&, int); - void renderDouble (std::vector &, int, Color&, double); - void renderStringLeft (std::vector &, int, Color&, const std::string&); - void renderStringRight (std::vector &, int, Color&, const std::string&); + virtual void measure(const std::string&, unsigned int&, unsigned int&) {}; + virtual void measure(Task&, unsigned int&, unsigned int&) {}; + virtual void renderHeader(std::vector&, int, Color&); + virtual void render(std::vector&, const std::string&, int, Color&) {}; + virtual void render(std::vector&, Task&, int, Color&) {}; + virtual bool validate(const std::string&) const { return false; }; + virtual void modify(Task&, const std::string&) {}; -protected: + protected: + void renderInteger(std::vector&, int, Color&, int); + void renderDouble(std::vector&, int, Color&, double); + void renderStringLeft(std::vector&, int, Color&, const std::string&); + void renderStringRight(std::vector&, int, Color&, const std::string&); + + protected: std::string _name; std::string _type; std::string _style; @@ -80,8 +80,8 @@ protected: bool _modifiable; bool _uda; bool _fixed_width; - std::vector _styles; - std::vector _examples; + std::vector _styles; + std::vector _examples; }; #endif diff --git a/src/commands/CMakeLists.txt b/src/commands/CMakeLists.txt index c71718d22..720b6cbc9 100644 --- a/src/commands/CMakeLists.txt +++ b/src/commands/CMakeLists.txt @@ -1,11 +1,9 @@ cmake_minimum_required (VERSION 3.22) include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/tc ${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/libshared/src - ${CMAKE_SOURCE_DIR}/taskchampion/lib ${TASK_INCLUDE_DIRS}) set (commands_SRCS Command.cpp Command.h @@ -37,6 +35,7 @@ set (commands_SRCS Command.cpp Command.h CmdHistory.cpp CmdHistory.h CmdIDs.cpp CmdIDs.h CmdImport.cpp CmdImport.h + CmdImportV2.cpp CmdImportV2.h CmdInfo.cpp CmdInfo.h CmdLog.cpp CmdLog.h CmdLogo.cpp CmdLogo.h @@ -61,6 +60,7 @@ set (commands_SRCS Command.cpp Command.h CmdVersion.cpp CmdVersion.h) add_library (commands STATIC ${commands_SRCS}) +target_link_libraries(commands taskchampion-cpp) #SET(CMAKE_BUILD_TYPE gcov) #SET(CMAKE_CXX_FLAGS_GCOV "--coverage") diff --git a/src/commands/CmdAdd.cpp b/src/commands/CmdAdd.cpp index 21d5475a9..f951083dc 100644 --- a/src/commands/CmdAdd.cpp +++ b/src/commands/CmdAdd.cpp @@ -25,67 +25,69 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include +#include #include -#include //////////////////////////////////////////////////////////////////////////////// -CmdAdd::CmdAdd () -{ - _keyword = "add"; - _usage = "task add "; - _description = "Adds a new task"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = false; +CmdAdd::CmdAdd() { + _keyword = "add"; + _usage = "task add "; + _description = "Adds a new task"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = false; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdAdd::execute (std::string& output) -{ +int CmdAdd::execute(std::string& output) { // Apply the command line modifications to the new task. Task task; // the task is empty, but DOM references can refer to earlier parts of the // command line, e.g., `task add due:20110101 wait:due`. - task.modify (Task::modReplace, true); - Context::getContext ().tdb2.add (task); + task.modify(Task::modReplace, true); + + // Validate a task for addition. This is stricter than `task.validate`, as any + // inconsistency is probably user error. + task.validate_add(); + + Context::getContext().tdb2.add(task); // Do not display ID 0, users cannot query by that - auto status = task.getStatus (); + auto status = task.getStatus(); // We may have a situation where both new-id and new-uuid config // variables are set. In that case, we'll show the new-uuid, as // it's enduring and never changes, and it's unlikely the caller // asked for this if they just wanted a human-friendly number. - if (Context::getContext ().verbose ("new-uuid") && - status == Task::recurring) - output += format ("Created task {1} (recurrence template).\n", task.get ("uuid")); + if (Context::getContext().verbose("new-uuid") && status == Task::recurring) + output += format("Created task {1} (recurrence template).\n", task.get("uuid")); - else if (Context::getContext ().verbose ("new-uuid") || - (Context::getContext ().verbose ("new-id") && - (status == Task::completed || - status == Task::deleted))) - output += format ("Created task {1}.\n", task.get ("uuid")); + else if (Context::getContext().verbose("new-uuid") || + (Context::getContext().verbose("new-id") && + (status == Task::completed || status == Task::deleted))) + output += format("Created task {1}.\n", task.get("uuid")); - else if (Context::getContext ().verbose ("new-id") && - (status == Task::pending || - status == Task::waiting)) - output += format ("Created task {1}.\n", task.id); + else if (Context::getContext().verbose("new-id") && + (status == Task::pending || status == Task::waiting)) + output += format("Created task {1}.\n", task.id); - else if (Context::getContext ().verbose ("new-id") && - status == Task::recurring) - output += format ("Created task {1} (recurrence template).\n", task.id); + else if (Context::getContext().verbose("new-id") && status == Task::recurring) + output += format("Created task {1} (recurrence template).\n", task.id); - if (Context::getContext ().verbose ("project")) - Context::getContext ().footnote (onProjectChange (task)); + if (Context::getContext().verbose("project")) + Context::getContext().footnote(onProjectChange(task)); return 0; } diff --git a/src/commands/CmdAdd.h b/src/commands/CmdAdd.h index 995938091..e67f7d17e 100644 --- a/src/commands/CmdAdd.h +++ b/src/commands/CmdAdd.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDADD #define INCLUDED_CMDADD -#include #include -class CmdAdd : public Command -{ -public: - CmdAdd (); - int execute (std::string&); +#include + +class CmdAdd : public Command { + public: + CmdAdd(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdAliases.cpp b/src/commands/CmdAliases.cpp index e6e1b8e5e..758365a95 100644 --- a/src/commands/CmdAliases.cpp +++ b/src/commands/CmdAliases.cpp @@ -25,32 +25,32 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdCompletionAliases::CmdCompletionAliases () -{ - _keyword = "_aliases"; - _usage = "task _aliases"; - _description = "Generates a list of all aliases, for autocompletion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionAliases::CmdCompletionAliases() { + _keyword = "_aliases"; + _usage = "task _aliases"; + _description = "Generates a list of all aliases, for autocompletion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionAliases::execute (std::string& output) -{ - for (const auto& alias : Context::getContext ().config) - if (alias.first.substr (0, 6) == "alias.") - output += alias.first.substr (6) + '\n'; +int CmdCompletionAliases::execute(std::string& output) { + for (const auto& alias : Context::getContext().config) + if (alias.first.substr(0, 6) == "alias.") output += alias.first.substr(6) + '\n'; return 0; } diff --git a/src/commands/CmdAliases.h b/src/commands/CmdAliases.h index a6fc9bf2a..e8fdd7c9e 100644 --- a/src/commands/CmdAliases.h +++ b/src/commands/CmdAliases.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDALIASES #define INCLUDED_CMDALIASES -#include #include -class CmdCompletionAliases : public Command -{ -public: - CmdCompletionAliases (); - int execute (std::string&); +#include + +class CmdCompletionAliases : public Command { + public: + CmdCompletionAliases(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdAnnotate.cpp b/src/commands/CmdAnnotate.cpp index 4a3a17a83..efee759ea 100644 --- a/src/commands/CmdAnnotate.cpp +++ b/src/commands/CmdAnnotate.cpp @@ -25,112 +25,104 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include -#include +#include #include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdAnnotate::CmdAnnotate () -{ - _keyword = "annotate"; - _usage = "task annotate "; - _description = "Adds an annotation to an existing task"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = true; +CmdAnnotate::CmdAnnotate() { + _keyword = "annotate"; + _usage = "task annotate "; + _description = "Adds an annotation to an existing task"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdAnnotate::execute (std::string&) -{ +int CmdAnnotate::execute(std::string&) { int rc = 0; int count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // TODO Complain when no modifications are specified. // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); + for (auto& task : filtered) { + Task before(task); // Annotate the specified task. - std::string question = format ("Annotate task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + std::string question = + format("Annotate task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modAnnotate, true); + task.modify(Task::modAnnotate, true); - if (permission (before.diff (task) + question, filtered.size ())) - { - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Annotating task {1} '{2}'.", task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); + feedback_affected("Annotating task {1} '{2}'.", task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); // Annotate siblings. - if (task.has ("parent")) - { - if ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm ("This is a recurring task. Do you want to annotate all pending recurrences of this same task?")) || - Context::getContext ().config.getBoolean ("recurrence.confirmation")) - { - auto siblings = Context::getContext ().tdb2.siblings (task); - for (auto& sibling : siblings) - { - sibling.modify (Task::modAnnotate, true); - Context::getContext ().tdb2.modify (sibling); + if (task.has("parent")) { + if ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm("This is a recurring task. Do you want to annotate all pending recurrences " + "of this same task?")) || + Context::getContext().config.getBoolean("recurrence.confirmation")) { + auto siblings = Context::getContext().tdb2.siblings(task); + for (auto& sibling : siblings) { + sibling.modify(Task::modAnnotate, true); + Context::getContext().tdb2.modify(sibling); ++count; - feedback_affected ("Annotating recurring task {1} '{2}'.", sibling); + feedback_affected("Annotating recurring task {1} '{2}'.", sibling); } // Annotate the parent Task parent; - Context::getContext ().tdb2.get (task.get ("parent"), parent); - parent.modify (Task::modAnnotate, true); - Context::getContext ().tdb2.modify (parent); + Context::getContext().tdb2.get(task.get("parent"), parent); + parent.modify(Task::modAnnotate, true); + Context::getContext().tdb2.modify(parent); } } - } - else - { + } else { std::cout << "Task not annotated.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } } // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Annotated {1} task." : "Annotated {1} tasks.", count); + feedback_affected(count == 1 ? "Annotated {1} task." : "Annotated {1} tasks.", count); return rc; } diff --git a/src/commands/CmdAnnotate.h b/src/commands/CmdAnnotate.h index db0c47c4e..97f3973df 100644 --- a/src/commands/CmdAnnotate.h +++ b/src/commands/CmdAnnotate.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDANNOTATE #define INCLUDED_CMDANNOTATE -#include #include -class CmdAnnotate : public Command -{ -public: - CmdAnnotate (); - int execute (std::string&); +#include + +class CmdAnnotate : public Command { + public: + CmdAnnotate(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdAppend.cpp b/src/commands/CmdAppend.cpp index 1ce75447b..0cdad990b 100644 --- a/src/commands/CmdAppend.cpp +++ b/src/commands/CmdAppend.cpp @@ -25,112 +25,104 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include #include -#include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdAppend::CmdAppend () -{ - _keyword = "append"; - _usage = "task append "; - _description = "Appends text to an existing task description"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = true; +CmdAppend::CmdAppend() { + _keyword = "append"; + _usage = "task append "; + _description = "Appends text to an existing task description"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdAppend::execute (std::string&) -{ +int CmdAppend::execute(std::string&) { int rc = 0; int count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // TODO Complain when no modifications are specified. // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); + for (auto& task : filtered) { + Task before(task); // Append to the specified task. - auto question = format ("Append to task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + auto question = + format("Append to task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modAppend, true); + task.modify(Task::modAppend, true); - if (permission (before.diff (task) + question, filtered.size ())) - { - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Appending to task {1} '{2}'.", task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); + feedback_affected("Appending to task {1} '{2}'.", task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); // Append to siblings. - if (task.has ("parent")) - { - if ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm ("This is a recurring task. Do you want to append to all pending recurrences of this same task?")) || - Context::getContext ().config.getBoolean ("recurrence.confirmation")) - { - std::vector siblings = Context::getContext ().tdb2.siblings (task); - for (auto& sibling : siblings) - { - sibling.modify (Task::modAppend, true); - Context::getContext ().tdb2.modify (sibling); + if (task.has("parent")) { + if ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm("This is a recurring task. Do you want to append to all pending recurrences " + "of this same task?")) || + Context::getContext().config.getBoolean("recurrence.confirmation")) { + std::vector siblings = Context::getContext().tdb2.siblings(task); + for (auto& sibling : siblings) { + sibling.modify(Task::modAppend, true); + Context::getContext().tdb2.modify(sibling); ++count; - feedback_affected ("Appending to recurring task {1} '{2}'.", sibling); + feedback_affected("Appending to recurring task {1} '{2}'.", sibling); } // Append to the parent Task parent; - Context::getContext ().tdb2.get (task.get ("parent"), parent); - parent.modify (Task::modAppend, true); - Context::getContext ().tdb2.modify (parent); + Context::getContext().tdb2.get(task.get("parent"), parent); + parent.modify(Task::modAppend, true); + Context::getContext().tdb2.modify(parent); } } - } - else - { + } else { std::cout << "Task not appended.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } } // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Appended {1} task." : "Appended {1} tasks.", count); + feedback_affected(count == 1 ? "Appended {1} task." : "Appended {1} tasks.", count); return rc; } diff --git a/src/commands/CmdAppend.h b/src/commands/CmdAppend.h index a3a6b3a86..41428926f 100644 --- a/src/commands/CmdAppend.h +++ b/src/commands/CmdAppend.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDAPPEND #define INCLUDED_CMDAPPEND -#include #include -class CmdAppend : public Command -{ -public: - CmdAppend (); - int execute (std::string&); +#include + +class CmdAppend : public Command { + public: + CmdAppend(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdAttributes.cpp b/src/commands/CmdAttributes.cpp index 4088d6391..553b9476d 100644 --- a/src/commands/CmdAttributes.cpp +++ b/src/commands/CmdAttributes.cpp @@ -25,40 +25,41 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdZshAttributes::CmdZshAttributes () -{ - _keyword = "_zshattributes"; - _usage = "task _zshattributes"; - _description = "Generates a list of all attributes, for zsh autocompletion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdZshAttributes::CmdZshAttributes() { + _keyword = "_zshattributes"; + _usage = "task _zshattributes"; + _description = "Generates a list of all attributes, for zsh autocompletion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdZshAttributes::execute (std::string& output) -{ +int CmdZshAttributes::execute(std::string& output) { // Get a list of all columns, sort them. - auto columns = Context::getContext ().getColumns (); - std::sort (columns.begin (), columns.end ()); + auto columns = Context::getContext().getColumns(); + std::sort(columns.begin(), columns.end()); std::stringstream out; - for (const auto& col : columns) - out << col << ':' << col << '\n'; + for (const auto& col : columns) out << col << ':' << col << '\n'; - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdAttributes.h b/src/commands/CmdAttributes.h index 72c56779a..fa3ef8d81 100644 --- a/src/commands/CmdAttributes.h +++ b/src/commands/CmdAttributes.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDATTRIBUTES #define INCLUDED_CMDATTRIBUTES -#include #include -class CmdZshAttributes : public Command -{ -public: - CmdZshAttributes (); - int execute (std::string&); +#include + +class CmdZshAttributes : public Command { + public: + CmdZshAttributes(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdBurndown.cpp b/src/commands/CmdBurndown.cpp index be9a252be..b0045cab8 100644 --- a/src/commands/CmdBurndown.cpp +++ b/src/commands/CmdBurndown.cpp @@ -25,63 +25,59 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include -#include #include -#include #include #include -#include -#include +#include #include +#include +#include +#include + +#include +#include +#include +#include // Helper macro. -#define LOC(y,x) (((y) * (_width + 1)) + (x)) +#define LOC(y, x) (((y) * (_width + 1)) + (x)) //////////////////////////////////////////////////////////////////////////////// -class Bar -{ -public: - Bar () = default; - Bar (const Bar&); - Bar& operator= (const Bar&); - ~Bar () = default; +class Bar { + public: + Bar() = default; + Bar(const Bar&); + Bar& operator=(const Bar&); + ~Bar() = default; -public: - int _offset {0}; // from left of chart - std::string _major_label {""}; // x-axis label, major (year/-/month) - std::string _minor_label {""}; // x-axis label, minor (month/week/day) - int _pending {0}; // Number of pending tasks in period - int _started {0}; // Number of started tasks in period - int _done {0}; // Number of done tasks in period - int _added {0}; // Number added in period - int _removed {0}; // Number removed in period + public: + int _offset{0}; // from left of chart + std::string _major_label{""}; // x-axis label, major (year/-/month) + std::string _minor_label{""}; // x-axis label, minor (month/week/day) + int _pending{0}; // Number of pending tasks in period + int _started{0}; // Number of started tasks in period + int _done{0}; // Number of done tasks in period + int _added{0}; // Number added in period + int _removed{0}; // Number removed in period }; //////////////////////////////////////////////////////////////////////////////// -Bar::Bar (const Bar& other) -{ - *this = other; -} +Bar::Bar(const Bar& other) { *this = other; } //////////////////////////////////////////////////////////////////////////////// -Bar& Bar::operator= (const Bar& other) -{ - if (this != &other) - { - _offset = other._offset; +Bar& Bar::operator=(const Bar& other) { + if (this != &other) { + _offset = other._offset; _major_label = other._major_label; _minor_label = other._minor_label; - _pending = other._pending; - _started = other._started; - _done = other._done; - _added = other._added; - _removed = other._removed; + _pending = other._pending; + _started = other._started; + _done = other._done; + _added = other._added; + _removed = other._removed; } return *this; @@ -118,62 +114,60 @@ Bar& Bar::operator= (const Bar& other) // 30 31 01 02 03 04 05 06 07 08 09 10 // Oct Nov // -class Chart -{ -public: - Chart (char); - Chart (const Chart&); // Unimplemented - Chart& operator= (const Chart&); // Unimplemented - ~Chart () = default; +class Chart { + public: + Chart(char); + Chart(const Chart&); // Unimplemented + Chart& operator=(const Chart&); // Unimplemented + ~Chart() = default; - void scan (std::vector &); - void scanForPeak (std::vector &); - std::string render (); + void scan(std::vector&); + void scanForPeak(std::vector&); + std::string render(); -private: - void generateBars (); - void optimizeGrid (); - Datetime quantize (const Datetime&, char); + private: + void generateBars(); + void optimizeGrid(); + Datetime quantize(const Datetime&, char); - Datetime increment (const Datetime&, char); - Datetime decrement (const Datetime&, char); - void maxima (); - void yLabels (std::vector &); - void calculateRates (); - unsigned round_up_to (unsigned, unsigned); - unsigned burndown_size (unsigned); + Datetime increment(const Datetime&, char); + Datetime decrement(const Datetime&, char); + void maxima(); + void yLabels(std::vector&); + void calculateRates(); + unsigned round_up_to(unsigned, unsigned); + unsigned burndown_size(unsigned); -public: - int _width {}; // Terminal width - int _height {}; // Terminal height - int _graph_width {}; // Width of plot area - int _graph_height {}; // Height of plot area - int _max_value {0}; // Largest combined bar value - int _max_label {1}; // Longest y-axis label - std::vector _labels {}; // Y-axis labels - int _estimated_bars {}; // Estimated bar count - int _actual_bars {0}; // Calculated bar count - std::map _bars {}; // Epoch-indexed set of bars - Datetime _earliest {}; // Date of earliest estimated bar - int _carryover_done {0}; // Number of 'done' tasks prior to chart range - char _period {}; // D, W, M - std::string _grid {}; // String representing grid of characters - time_t _peak_epoch {}; // Quantized (D) date of highest pending peak - int _peak_count {0}; // Corresponding peak pending count - int _current_count {0}; // Current pending count - float _net_fix_rate {0.0}; // Calculated fix rate - std::string _completion {}; // Estimated completion date + public: + int _width{}; // Terminal width + int _height{}; // Terminal height + int _graph_width{}; // Width of plot area + int _graph_height{}; // Height of plot area + int _max_value{0}; // Largest combined bar value + int _max_label{1}; // Longest y-axis label + std::vector _labels{}; // Y-axis labels + int _estimated_bars{}; // Estimated bar count + int _actual_bars{0}; // Calculated bar count + std::map _bars{}; // Epoch-indexed set of bars + Datetime _earliest{}; // Date of earliest estimated bar + int _carryover_done{0}; // Number of 'done' tasks prior to chart range + char _period{}; // D, W, M + std::string _grid{}; // String representing grid of characters + time_t _peak_epoch{}; // Quantized (D) date of highest pending peak + int _peak_count{0}; // Corresponding peak pending count + int _current_count{0}; // Current pending count + float _net_fix_rate{0.0}; // Calculated fix rate + std::string _completion{}; // Estimated completion date }; //////////////////////////////////////////////////////////////////////////////// -Chart::Chart (char type) -{ +Chart::Chart(char type) { // How much space is there to render in? This chart will occupy the // maximum space, and the width drives various other parameters. - _width = Context::getContext ().getWidth (); - _height = Context::getContext ().getHeight () - - Context::getContext ().config.getInteger ("reserved.lines") - - 1; // Allow for new line with prompt. + _width = Context::getContext().getWidth(); + _height = Context::getContext().getHeight() - + Context::getContext().config.getInteger("reserved.lines") - + 1; // Allow for new line with prompt. _graph_height = _height - 7; _graph_width = _width - _max_label - 14; @@ -187,39 +181,34 @@ Chart::Chart (char type) //////////////////////////////////////////////////////////////////////////////// // Scan all tasks, quantize the dates by day, and find the peak pending count // and corresponding epoch. -void Chart::scanForPeak (std::vector & tasks) -{ - std::map pending; +void Chart::scanForPeak(std::vector& tasks) { + std::map pending; _current_count = 0; - for (auto& task : tasks) - { + for (auto& task : tasks) { // The entry date is when the counting starts. - Datetime entry (task.get_date ("entry")); + Datetime entry(task.get_date("entry")); Datetime end; - if (task.has ("end")) - end = Datetime (task.get_date ("end")); + if (task.has("end")) + end = Datetime(task.get_date("end")); else ++_current_count; - while (entry < end) - { - time_t epoch = quantize (entry.toEpoch (), 'D').toEpoch (); - if (pending.find (epoch) != pending.end ()) + while (entry < end) { + time_t epoch = quantize(entry.toEpoch(), 'D').toEpoch(); + if (pending.find(epoch) != pending.end()) ++pending[epoch]; else pending[epoch] = 1; - entry = increment (entry, 'D'); + entry = increment(entry, 'D'); } } // Find the peak and peak date. - for (auto& count : pending) - { - if (count.second > _peak_count) - { + for (auto& count : pending) { + if (count.second > _peak_count) { _peak_count = count.second; _peak_epoch = count.first; } @@ -227,120 +216,93 @@ void Chart::scanForPeak (std::vector & tasks) } //////////////////////////////////////////////////////////////////////////////// -void Chart::scan (std::vector & tasks) -{ - generateBars (); +void Chart::scan(std::vector& tasks) { + generateBars(); // Not quantized, so that "while (xxx < now)" is inclusive. Datetime now; time_t epoch; - auto& config = Context::getContext ().config; + auto& config = Context::getContext().config; bool cumulative; - if (config.has ("burndown.cumulative")) - { - cumulative = config.getBoolean ("burndown.cumulative"); - } - else - { + if (config.has("burndown.cumulative")) { + cumulative = config.getBoolean("burndown.cumulative"); + } else { cumulative = true; } - for (auto& task : tasks) - { + for (auto& task : tasks) { // The entry date is when the counting starts. - Datetime from = quantize (Datetime (task.get_date ("entry")), _period); - epoch = from.toEpoch (); + Datetime from = quantize(Datetime(task.get_date("entry")), _period); + epoch = from.toEpoch(); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._added; + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._added; // e--> e--s--> // ppp> pppsss> - Task::status status = task.getStatus (); - if (status == Task::pending || - status == Task::waiting) - { - if (task.has ("start")) - { - Datetime start = quantize (Datetime (task.get_date ("start")), _period); - while (from < start) - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._pending; - from = increment (from, _period); + Task::status status = task.getStatus(); + if (status == Task::pending || status == Task::waiting) { + if (task.has("start")) { + Datetime start = quantize(Datetime(task.get_date("start")), _period); + while (from < start) { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._pending; + from = increment(from, _period); } - while (from < now) - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._started; - from = increment (from, _period); + while (from < now) { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._started; + from = increment(from, _period); } - } - else - { - while (from < now) - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._pending; - from = increment (from, _period); + } else { + while (from < now) { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._pending; + from = increment(from, _period); } } } // e--C e--s--C // pppd> pppsssd> - else if (status == Task::completed) - { + else if (status == Task::completed) { // Truncate history so it starts at 'earliest' for completed tasks. - Datetime end = quantize (Datetime (task.get_date ("end")), _period); - epoch = end.toEpoch (); + Datetime end = quantize(Datetime(task.get_date("end")), _period); + epoch = end.toEpoch(); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._removed; + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._removed; - while (from < end) - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._pending; - from = increment (from, _period); + while (from < end) { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._pending; + from = increment(from, _period); } - if (cumulative) - { - while (from < now) - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._done; - from = increment (from, _period); + if (cumulative) { + while (from < now) { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._done; + from = increment(from, _period); } // Maintain a running total of 'done' tasks that are off the left of the // chart. - if (end < _earliest) - { + if (end < _earliest) { ++_carryover_done; continue; } } - else - { - epoch = from.toEpoch (); - if (_bars.find (epoch) != _bars.end ()) - ++_bars[epoch]._done; + else { + epoch = from.toEpoch(); + if (_bars.find(epoch) != _bars.end()) ++_bars[epoch]._done; } } } // Size the data. - maxima (); + maxima(); } //////////////////////////////////////////////////////////////////////////////// @@ -362,153 +324,146 @@ void Chart::scan (std::vector & tasks) // | ADD rate 1.7/d Estimated completion 8/12/2010 | // | Don/Delete rate 1.3/d | // +---------------------------------------------------------------------+ -std::string Chart::render () -{ - if (_graph_height < 5 || // a 4-line graph is essentially unreadable. - _graph_width < 2) // A single-bar graph is useless. +std::string Chart::render() { + if (_graph_height < 5 || // a 4-line graph is essentially unreadable. + _graph_width < 2) // A single-bar graph is useless. { - return std::string ("Terminal window too small to draw a graph.\n"); + return std::string("Terminal window too small to draw a graph.\n"); } - else if (_graph_height > 1000 || // each line is a string allloc - _graph_width > 1000) - { - return std::string ("Terminal window too large to draw a graph.\n"); + else if (_graph_height > 1000 || // each line is a string allloc + _graph_width > 1000) { + return std::string("Terminal window too large to draw a graph.\n"); } - if (_max_value == 0) - Context::getContext ().footnote ("No matches."); + if (_max_value == 0) Context::getContext().footnote("No matches."); // Create a grid, folded into a string. _grid = ""; - for (int i = 0; i < _height; ++i) - _grid += std::string (_width, ' ') + '\n'; + for (int i = 0; i < _height; ++i) _grid += std::string(_width, ' ') + '\n'; // Title. - std::string title = _period == 'D' ? "Daily" - : _period == 'W' ? "Weekly" - : "Monthly"; - title += std::string (" Burndown"); - _grid.replace (LOC (0, (_width - title.length ()) / 2), title.length (), title); + std::string title = _period == 'D' ? "Daily" : _period == 'W' ? "Weekly" : "Monthly"; + title += std::string(" Burndown"); + _grid.replace(LOC(0, (_width - title.length()) / 2), title.length(), title); // Legend. - _grid.replace (LOC (_graph_height / 2 - 1, _width - 10), 10, "DD " + leftJustify ("Done", 7)); - _grid.replace (LOC (_graph_height / 2, _width - 10), 10, "SS " + leftJustify ("Started", 7)); - _grid.replace (LOC (_graph_height / 2 + 1, _width - 10), 10, "PP " + leftJustify ("Pending", 7)); + _grid.replace(LOC(_graph_height / 2 - 1, _width - 10), 10, "DD " + leftJustify("Done", 7)); + _grid.replace(LOC(_graph_height / 2, _width - 10), 10, "SS " + leftJustify("Started", 7)); + _grid.replace(LOC(_graph_height / 2 + 1, _width - 10), 10, "PP " + leftJustify("Pending", 7)); // Determine y-axis labelling. - std::vector _labels; - yLabels (_labels); - _max_label = (int) log10 ((double) _labels[2]) + 1; + std::vector _labels; + yLabels(_labels); + _max_label = (int)log10((double)_labels[2]) + 1; // Draw y-axis. - for (int i = 0; i < _graph_height; ++i) - _grid.replace (LOC (i + 1, _max_label + 1), 1, "|"); + for (int i = 0; i < _graph_height; ++i) _grid.replace(LOC(i + 1, _max_label + 1), 1, "|"); // Draw y-axis labels. - char label [12]; - snprintf (label, 12, "%*d", _max_label, _labels[2]); - _grid.replace (LOC (1, _max_label - strlen (label)), strlen (label), label); - snprintf (label, 12, "%*d", _max_label, _labels[1]); - _grid.replace (LOC (1 + (_graph_height / 2), _max_label - strlen (label)), strlen (label), label); - _grid.replace (LOC (_graph_height + 1, _max_label - 1), 1, "0"); + char label[12]; + snprintf(label, 12, "%*d", _max_label, _labels[2]); + _grid.replace(LOC(1, _max_label - strlen(label)), strlen(label), label); + snprintf(label, 12, "%*d", _max_label, _labels[1]); + _grid.replace(LOC(1 + (_graph_height / 2), _max_label - strlen(label)), strlen(label), label); + _grid.replace(LOC(_graph_height + 1, _max_label - 1), 1, "0"); // Draw x-axis. - _grid.replace (LOC (_height - 6, _max_label + 1), 1, "+"); - _grid.replace (LOC (_height - 6, _max_label + 2), _graph_width, std::string (_graph_width, '-')); + _grid.replace(LOC(_height - 6, _max_label + 1), 1, "+"); + _grid.replace(LOC(_height - 6, _max_label + 2), _graph_width, std::string(_graph_width, '-')); // Draw x-axis labels. - std::vector bars_in_sequence; - for (auto& bar : _bars) - bars_in_sequence.push_back (bar.first); + std::vector bars_in_sequence; + for (auto& bar : _bars) bars_in_sequence.push_back(bar.first); - std::sort (bars_in_sequence.begin (), bars_in_sequence.end ()); + std::sort(bars_in_sequence.begin(), bars_in_sequence.end()); std::string _major_label; - for (auto& seq : bars_in_sequence) - { + for (auto& seq : bars_in_sequence) { Bar bar = _bars[seq]; // If it fits within the allowed space. - if (bar._offset < _actual_bars) - { - _grid.replace (LOC (_height - 5, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), bar._minor_label.length (), bar._minor_label); + if (bar._offset < _actual_bars) { + _grid.replace(LOC(_height - 5, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), + bar._minor_label.length(), bar._minor_label); if (_major_label != bar._major_label) - _grid.replace (LOC (_height - 4, _max_label + 2 + ((_actual_bars - bar._offset - 1) * 3)), bar._major_label.length (), ' ' + bar._major_label); + _grid.replace(LOC(_height - 4, _max_label + 2 + ((_actual_bars - bar._offset - 1) * 3)), + bar._major_label.length(), ' ' + bar._major_label); _major_label = bar._major_label; } } // Draw bars. - for (auto& seq : bars_in_sequence) - { + for (auto& seq : bars_in_sequence) { Bar bar = _bars[seq]; // If it fits within the allowed space. - if (bar._offset < _actual_bars) - { - int pending = ( bar._pending * _graph_height) / _labels[2]; - int started = ((bar._pending + bar._started) * _graph_height) / _labels[2]; - int done = ((bar._pending + bar._started + bar._done + _carryover_done) * _graph_height) / _labels[2]; + if (bar._offset < _actual_bars) { + int pending = (bar._pending * _graph_height) / _labels[2]; + int started = ((bar._pending + bar._started) * _graph_height) / _labels[2]; + int done = ((bar._pending + bar._started + bar._done + _carryover_done) * _graph_height) / + _labels[2]; for (int b = 0; b < pending; ++b) - _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "PP"); + _grid.replace( + LOC(_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, + "PP"); for (int b = pending; b < started; ++b) - _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "SS"); + _grid.replace( + LOC(_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, + "SS"); for (int b = started; b < done; ++b) - _grid.replace (LOC (_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, "DD"); + _grid.replace( + LOC(_graph_height - b, _max_label + 3 + ((_actual_bars - bar._offset - 1) * 3)), 2, + "DD"); } } // Draw rates. - calculateRates (); + calculateRates(); char rate[12]; if (_net_fix_rate != 0.0) - snprintf (rate, 12, "%.1f/d", _net_fix_rate); + snprintf(rate, 12, "%.1f/d", _net_fix_rate); else - strcpy (rate, "-"); + strcpy(rate, "-"); - _grid.replace (LOC (_height - 2, _max_label + 3), 22 + strlen (rate), std::string ("Net Fix Rate: ") + rate); + _grid.replace(LOC(_height - 2, _max_label + 3), 22 + strlen(rate), + std::string("Net Fix Rate: ") + rate); // Draw completion date. - if (_completion.length ()) - _grid.replace (LOC (_height - 1, _max_label + 3), 22 + _completion.length (), "Estimated completion: " + _completion); + if (_completion.length()) + _grid.replace(LOC(_height - 1, _max_label + 3), 22 + _completion.length(), + "Estimated completion: " + _completion); - optimizeGrid (); + optimizeGrid(); - if (Context::getContext ().color ()) - { + if (Context::getContext().color()) { // Colorize the grid. - Color color_pending (Context::getContext ().config.get ("color.burndown.pending")); - Color color_done (Context::getContext ().config.get ("color.burndown.done")); - Color color_started (Context::getContext ().config.get ("color.burndown.started")); + Color color_pending(Context::getContext().config.get("color.burndown.pending")); + Color color_done(Context::getContext().config.get("color.burndown.done")); + Color color_started(Context::getContext().config.get("color.burndown.started")); // Replace DD, SS, PP with colored strings. std::string::size_type i; - while ((i = _grid.find ("PP")) != std::string::npos) - _grid.replace (i, 2, color_pending.colorize (" ")); + while ((i = _grid.find("PP")) != std::string::npos) + _grid.replace(i, 2, color_pending.colorize(" ")); - while ((i = _grid.find ("SS")) != std::string::npos) - _grid.replace (i, 2, color_started.colorize (" ")); + while ((i = _grid.find("SS")) != std::string::npos) + _grid.replace(i, 2, color_started.colorize(" ")); - while ((i = _grid.find ("DD")) != std::string::npos) - _grid.replace (i, 2, color_done.colorize (" ")); - } - else - { + while ((i = _grid.find("DD")) != std::string::npos) + _grid.replace(i, 2, color_done.colorize(" ")); + } else { // Replace DD, SS, PP with ./+/X strings. std::string::size_type i; - while ((i = _grid.find ("PP")) != std::string::npos) - _grid.replace (i, 2, " X"); + while ((i = _grid.find("PP")) != std::string::npos) _grid.replace(i, 2, " X"); - while ((i = _grid.find ("SS")) != std::string::npos) - _grid.replace (i, 2, " +"); + while ((i = _grid.find("SS")) != std::string::npos) _grid.replace(i, 2, " +"); - while ((i = _grid.find ("DD")) != std::string::npos) - _grid.replace (i, 2, " ."); + while ((i = _grid.find("DD")) != std::string::npos) _grid.replace(i, 2, " ."); } return _grid; @@ -516,213 +471,189 @@ std::string Chart::render () //////////////////////////////////////////////////////////////////////////////// // _grid =~ /\s+$//g -void Chart::optimizeGrid () -{ +void Chart::optimizeGrid() { std::string::size_type ws; - while ((ws = _grid.find (" \n")) != std::string::npos) - { + while ((ws = _grid.find(" \n")) != std::string::npos) { auto non_ws = ws; - while (_grid[non_ws] == ' ') - --non_ws; + while (_grid[non_ws] == ' ') --non_ws; - _grid.replace (non_ws + 1, ws - non_ws + 1, "\n"); + _grid.replace(non_ws + 1, ws - non_ws + 1, "\n"); } } //////////////////////////////////////////////////////////////////////////////// -Datetime Chart::quantize (const Datetime& input, char period) -{ - if (period == 'D') return input.startOfDay (); - if (period == 'W') return input.startOfWeek (); - if (period == 'M') return input.startOfMonth (); +Datetime Chart::quantize(const Datetime& input, char period) { + if (period == 'D') return input.startOfDay(); + if (period == 'W') return input.startOfWeek(); + if (period == 'M') return input.startOfMonth(); return input; } //////////////////////////////////////////////////////////////////////////////// -Datetime Chart::increment (const Datetime& input, char period) -{ +Datetime Chart::increment(const Datetime& input, char period) { // Move to the next period. - int d = input.day (); - int m = input.month (); - int y = input.year (); + int d = input.day(); + int m = input.month(); + int y = input.year(); int days; - switch (period) - { - case 'D': - if (++d > Datetime::daysInMonth (y, m)) - { + switch (period) { + case 'D': + if (++d > Datetime::daysInMonth(y, m)) { + d = 1; + + if (++m == 13) { + m = 1; + ++y; + } + } + break; + + case 'W': + d += 7; + days = Datetime::daysInMonth(y, m); + if (d > days) { + d -= days; + + if (++m == 13) { + m = 1; + ++y; + } + } + break; + + case 'M': d = 1; - - if (++m == 13) - { + if (++m == 13) { m = 1; ++y; } - } - break; - - case 'W': - d += 7; - days = Datetime::daysInMonth (y, m); - if (d > days) - { - d -= days; - - if (++m == 13) - { - m = 1; - ++y; - } - } - break; - - case 'M': - d = 1; - if (++m == 13) - { - m = 1; - ++y; - } - break; + break; } - return Datetime (y, m, d, 0, 0, 0); + return Datetime(y, m, d, 0, 0, 0); } //////////////////////////////////////////////////////////////////////////////// -Datetime Chart::decrement (const Datetime& input, char period) -{ +Datetime Chart::decrement(const Datetime& input, char period) { // Move to the previous period. - int d = input.day (); - int m = input.month (); - int y = input.year (); + int d = input.day(); + int m = input.month(); + int y = input.year(); - switch (period) - { - case 'D': - if (--d == 0) - { - if (--m == 0) - { + switch (period) { + case 'D': + if (--d == 0) { + if (--m == 0) { + m = 12; + --y; + } + + d = Datetime::daysInMonth(y, m); + } + break; + + case 'W': + d -= 7; + if (d < 1) { + if (--m == 0) { + m = 12; + y--; + } + + d += Datetime::daysInMonth(y, m); + } + break; + + case 'M': + d = 1; + if (--m == 0) { m = 12; --y; } - - d = Datetime::daysInMonth (y, m); - } - break; - - case 'W': - d -= 7; - if (d < 1) - { - if (--m == 0) - { - m = 12; - y--; - } - - d += Datetime::daysInMonth (y, m); - } - break; - - case 'M': - d = 1; - if (--m == 0) - { - m = 12; - --y; - } - break; + break; } - return Datetime (y, m, d, 0, 0, 0); + return Datetime(y, m, d, 0, 0, 0); } //////////////////////////////////////////////////////////////////////////////// // Do '_bars[epoch] = Bar' for every bar that may appear on a chart. -void Chart::generateBars () -{ +void Chart::generateBars() { Bar bar; // Determine the last bar date. Datetime cursor; - switch (_period) - { - case 'D': cursor = Datetime ().startOfDay (); break; - case 'W': cursor = Datetime ().startOfWeek (); break; - case 'M': cursor = Datetime ().startOfMonth (); break; + switch (_period) { + case 'D': + cursor = Datetime().startOfDay(); + break; + case 'W': + cursor = Datetime().startOfWeek(); + break; + case 'M': + cursor = Datetime().startOfMonth(); + break; } // Iterate and determine all the other bar dates. char str[12]; - for (int i = 0; i < _estimated_bars; ++i) - { + for (int i = 0; i < _estimated_bars; ++i) { // Create the major and minor labels. - switch (_period) - { - case 'D': // month/day + switch (_period) { + case 'D': // month/day { - std::string month = Datetime::monthName (cursor.month ()); - bar._major_label = month.substr (0, 3); + std::string month = Datetime::monthName(cursor.month()); + bar._major_label = month.substr(0, 3); - snprintf (str, 12, "%02d", cursor.day ()); + snprintf(str, 12, "%02d", cursor.day()); bar._minor_label = str; - } - break; + } break; - case 'W': // year/week - snprintf (str, 12, "%d", cursor.year ()); - bar._major_label = str; + case 'W': // year/week + snprintf(str, 12, "%d", cursor.year()); + bar._major_label = str; - snprintf (str, 12, "%02d", cursor.week ()); - bar._minor_label = str; - break; + snprintf(str, 12, "%02d", cursor.week()); + bar._minor_label = str; + break; - case 'M': // year/month - snprintf (str, 12, "%d", cursor.year ()); - bar._major_label = str; + case 'M': // year/month + snprintf(str, 12, "%d", cursor.year()); + bar._major_label = str; - snprintf (str, 12, "%02d", cursor.month ()); - bar._minor_label = str; - break; + snprintf(str, 12, "%02d", cursor.month()); + bar._minor_label = str; + break; } bar._offset = i; - _bars[cursor.toEpoch ()] = bar; + _bars[cursor.toEpoch()] = bar; // Record the earliest date, for use as a cutoff when scanning data. _earliest = cursor; // Move to the previous period. - cursor = decrement (cursor, _period); + cursor = decrement(cursor, _period); } } //////////////////////////////////////////////////////////////////////////////// -void Chart::maxima () -{ +void Chart::maxima() { _max_value = 0; _max_label = 1; - for (auto& bar : _bars) - { + for (auto& bar : _bars) { // Determine _max_label. - int total = bar.second._pending + - bar.second._started + - bar.second._done + - _carryover_done; + int total = bar.second._pending + bar.second._started + bar.second._done + _carryover_done; // Determine _max_value. - if (total > _max_value) - _max_value = total; + if (total > _max_value) _max_value = total; - int length = (int) log10 ((double) total) + 1; - if (length > _max_label) - _max_label = length; + int length = (int)log10((double)total) + 1; + if (length > _max_label) _max_label = length; } // How many bars can be shown? @@ -733,241 +664,196 @@ void Chart::maxima () //////////////////////////////////////////////////////////////////////////////// // Given the vertical chart area size (graph_height), the largest value // (_max_value), populate a vector of labels for the y axis. -void Chart::yLabels (std::vector & labels) -{ +void Chart::yLabels(std::vector& labels) { // Calculate may Y using a nice algorithm that rounds the data. - int high = burndown_size (_max_value); + int high = burndown_size(_max_value); int half = high / 2; - labels.push_back (0); - labels.push_back (half); - labels.push_back (high); + labels.push_back(0); + labels.push_back(half); + labels.push_back(high); } //////////////////////////////////////////////////////////////////////////////// -void Chart::calculateRates () -{ +void Chart::calculateRates() { // Q: Why is this equation written out as a debug message? // A: People are going to want to know how the rates and the completion date // are calculated. This may also help debugging. std::stringstream peak_message; - peak_message << "Chart::calculateRates Maximum of " - << _peak_count - << " pending tasks on " - << (Datetime (_peak_epoch).toISO ()) - << ", with currently " - << _current_count + peak_message << "Chart::calculateRates Maximum of " << _peak_count << " pending tasks on " + << (Datetime(_peak_epoch).toISO()) << ", with currently " << _current_count << " pending tasks"; - Context::getContext ().debug (peak_message.str ()); + Context::getContext().debug(peak_message.str()); // If there are no current pending tasks, then it is meaningless to find // rates or estimated completion date. - if (_current_count == 0) - return; + if (_current_count == 0) return; // If there is a net fix rate, and the peak was at least three days ago. Datetime now; - Datetime peak (_peak_epoch); - if (_peak_count > _current_count && - (now - peak) > 3 * 86400) - { + Datetime peak(_peak_epoch); + if (_peak_count > _current_count && (now - peak) > 3 * 86400) { // Fixes per second. Not a large number. - auto fix_rate = 1.0 * (_peak_count - _current_count) / (now.toEpoch () - _peak_epoch); + auto fix_rate = 1.0 * (_peak_count - _current_count) / (now.toEpoch() - _peak_epoch); _net_fix_rate = fix_rate * 86400; std::stringstream rate_message; - rate_message << "Chart::calculateRates Net reduction is " - << (_peak_count - _current_count) - << " tasks in " - << Duration (now.toEpoch () - _peak_epoch).formatISO () - << " = " - << _net_fix_rate - << " tasks/d"; - Context::getContext ().debug (rate_message.str ()); + rate_message << "Chart::calculateRates Net reduction is " << (_peak_count - _current_count) + << " tasks in " << Duration(now.toEpoch() - _peak_epoch).formatISO() << " = " + << _net_fix_rate << " tasks/d"; + Context::getContext().debug(rate_message.str()); - Duration delta (static_cast (_current_count / fix_rate)); - Datetime end = now + delta.toTime_t (); + Duration delta(static_cast(_current_count / fix_rate)); + Datetime end = now + delta.toTime_t(); // Prefer dateformat.report over dateformat. - std::string format = Context::getContext ().config.get ("dateformat.report"); - if (format == "") - { - format = Context::getContext ().config.get ("dateformat"); - if (format == "") - format = "Y-M-D"; + std::string format = Context::getContext().config.get("dateformat.report"); + if (format == "") { + format = Context::getContext().config.get("dateformat"); + if (format == "") format = "Y-M-D"; } - _completion = end.toString (format) - + " (" - + delta.formatVague () - + ')'; + _completion = end.toString(format) + " (" + delta.formatVague() + ')'; std::stringstream completion_message; - completion_message << "Chart::calculateRates (" - << _current_count - << " tasks / " - << _net_fix_rate - << ") = " - << delta.format () - << " --> " - << end.toISO (); - Context::getContext ().debug (completion_message.str ()); - } - else - { + completion_message << "Chart::calculateRates (" << _current_count << " tasks / " + << _net_fix_rate << ") = " << delta.format() << " --> " << end.toISO(); + Context::getContext().debug(completion_message.str()); + } else { _completion = "No convergence"; } } //////////////////////////////////////////////////////////////////////////////// -unsigned Chart::round_up_to (unsigned n, unsigned target) -{ - return n + target - (n % target); -} +unsigned Chart::round_up_to(unsigned n, unsigned target) { return n + target - (n % target); } //////////////////////////////////////////////////////////////////////////////// -unsigned Chart::burndown_size (unsigned ntasks) -{ +unsigned Chart::burndown_size(unsigned ntasks) { // Nearest 2 - if (ntasks < 20) - return round_up_to (ntasks, 2); + if (ntasks < 20) return round_up_to(ntasks, 2); // Nearest 10 - if (ntasks < 50) - return round_up_to (ntasks, 10); + if (ntasks < 50) return round_up_to(ntasks, 10); // Nearest 20 - if (ntasks < 100) - return round_up_to (ntasks, 20); + if (ntasks < 100) return round_up_to(ntasks, 20); // Choose the number from here rounded up to the nearest 10% of the next // highest power of 10 or half of power of 10. - const auto count = (unsigned) log10 (static_cast(std::numeric_limits::max ())); + const auto count = (unsigned)log10(static_cast(std::numeric_limits::max())); unsigned half = 500; unsigned full = 1000; // We start at two because we handle 5, 10, 50, and 100 above. - for (unsigned i = 2; i < count; ++i) - { - if (ntasks < half) - return round_up_to (ntasks, half / 10); + for (unsigned i = 2; i < count; ++i) { + if (ntasks < half) return round_up_to(ntasks, half / 10); - if (ntasks < full) - return round_up_to (ntasks, full / 10); + if (ntasks < full) return round_up_to(ntasks, full / 10); half *= 10; full *= 10; } // Round up to max of unsigned. - return std::numeric_limits::max (); + return std::numeric_limits::max(); } //////////////////////////////////////////////////////////////////////////////// -CmdBurndownMonthly::CmdBurndownMonthly () -{ - _keyword = "burndown.monthly"; - _usage = "task burndown.monthly"; - _description = "Shows a graphical burndown chart, by month"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdBurndownMonthly::CmdBurndownMonthly() { + _keyword = "burndown.monthly"; + _usage = "task burndown.monthly"; + _description = "Shows a graphical burndown chart, by month"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// -int CmdBurndownMonthly::execute (std::string& output) -{ +int CmdBurndownMonthly::execute(std::string& output) { int rc = 0; // Scan the pending tasks, applying any filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Create a chart, scan the tasks, then render. - Chart chart ('M'); - chart.scanForPeak (filtered); - chart.scan (filtered); - output = chart.render (); + Chart chart('M'); + chart.scanForPeak(filtered); + chart.scan(filtered); + output = chart.render(); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdBurndownWeekly::CmdBurndownWeekly () -{ - _keyword = "burndown.weekly"; - _usage = "task burndown.weekly"; - _description = "Shows a graphical burndown chart, by week"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdBurndownWeekly::CmdBurndownWeekly() { + _keyword = "burndown.weekly"; + _usage = "task burndown.weekly"; + _description = "Shows a graphical burndown chart, by week"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// -int CmdBurndownWeekly::execute (std::string& output) -{ +int CmdBurndownWeekly::execute(std::string& output) { int rc = 0; // Scan the pending tasks, applying any filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Create a chart, scan the tasks, then render. - Chart chart ('W'); - chart.scanForPeak (filtered); - chart.scan (filtered); - output = chart.render (); + Chart chart('W'); + chart.scanForPeak(filtered); + chart.scan(filtered); + output = chart.render(); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdBurndownDaily::CmdBurndownDaily () -{ - _keyword = "burndown.daily"; - _usage = "task burndown.daily"; - _description = "Shows a graphical burndown chart, by day"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdBurndownDaily::CmdBurndownDaily() { + _keyword = "burndown.daily"; + _usage = "task burndown.daily"; + _description = "Shows a graphical burndown chart, by day"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// -int CmdBurndownDaily::execute (std::string& output) -{ +int CmdBurndownDaily::execute(std::string& output) { int rc = 0; // Scan the pending tasks, applying any filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Create a chart, scan the tasks, then render. - Chart chart ('D'); - chart.scanForPeak (filtered); - chart.scan (filtered); - output = chart.render (); + Chart chart('D'); + chart.scanForPeak(filtered); + chart.scan(filtered); + output = chart.render(); return rc; } diff --git a/src/commands/CmdBurndown.h b/src/commands/CmdBurndown.h index 50a2f5336..818b01ea2 100644 --- a/src/commands/CmdBurndown.h +++ b/src/commands/CmdBurndown.h @@ -27,28 +27,26 @@ #ifndef INCLUDED_CMDBURNDOWN #define INCLUDED_CMDBURNDOWN -#include #include -class CmdBurndownMonthly : public Command -{ -public: - CmdBurndownMonthly (); - int execute (std::string&); +#include + +class CmdBurndownMonthly : public Command { + public: + CmdBurndownMonthly(); + int execute(std::string&); }; -class CmdBurndownWeekly : public Command -{ -public: - CmdBurndownWeekly (); - int execute (std::string&); +class CmdBurndownWeekly : public Command { + public: + CmdBurndownWeekly(); + int execute(std::string&); }; -class CmdBurndownDaily : public Command -{ -public: - CmdBurndownDaily (); - int execute (std::string&); +class CmdBurndownDaily : public Command { + public: + CmdBurndownDaily(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdCalc.cpp b/src/commands/CmdCalc.cpp index bb79a54a4..562b6d300 100644 --- a/src/commands/CmdCalc.cpp +++ b/src/commands/CmdCalc.cpp @@ -24,53 +24,50 @@ // //////////////////////////////////////////////////////////////////////////////// -#include #include -#include +#include #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdCalc::CmdCalc () -{ - _keyword = "calc"; - _usage = "task calc "; - _description = "Calculator"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCalc::CmdCalc() { + _keyword = "calc"; + _usage = "task calc "; + _description = "Calculator"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdCalc::execute (std::string& output) -{ +int CmdCalc::execute(std::string& output) { // Configurable infix/postfix - bool infix {true}; - if (Context::getContext ().config.get ("expressions") == "postfix") - infix = false; + bool infix{true}; + if (Context::getContext().config.get("expressions") == "postfix") infix = false; // Create an evaluator with DOM access. Eval e; - e.addSource (domSource); - e.debug (Context::getContext ().config.getBoolean ("debug")); + e.addSource(domSource); + e.debug(Context::getContext().config.getBoolean("debug")); // Compile all the args into one expression. std::string expression; - for (const auto& word : Context::getContext ().cli2.getWords ()) - expression += word + ' '; + for (const auto& word : Context::getContext().cli2.getWords()) expression += word + ' '; // Evaluate according to preference. Variant result; if (infix) - e.evaluateInfixExpression (expression, result); + e.evaluateInfixExpression(expression, result); else - e.evaluatePostfixExpression (expression, result); + e.evaluatePostfixExpression(expression, result); - output = (std::string) result + '\n'; + output = (std::string)result + '\n'; return 0; } diff --git a/src/commands/CmdCalc.h b/src/commands/CmdCalc.h index 442e043b9..930abd45b 100644 --- a/src/commands/CmdCalc.h +++ b/src/commands/CmdCalc.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDCALC #define INCLUDED_CMDCALC -#include #include -class CmdCalc : public Command -{ -public: - CmdCalc (); - int execute (std::string&); +#include + +class CmdCalc : public Command { + public: + CmdCalc(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdCalendar.cpp b/src/commands/CmdCalendar.cpp index caf211ffc..c3b021298 100644 --- a/src/commands/CmdCalendar.cpp +++ b/src/commands/CmdCalendar.cpp @@ -25,52 +25,53 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include -#include #include -#include +#include #include -#include +#include +#include #include -#include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdCalendar::CmdCalendar () -{ - _keyword = "calendar"; - _usage = "task calendar [due| |] [y]"; - _description = "Shows a calendar, with due tasks marked"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = false; +CmdCalendar::CmdCalendar() { + _keyword = "calendar"; + _usage = "task calendar [due| |] [y]"; + _description = "Shows a calendar, with due tasks marked"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// -int CmdCalendar::execute (std::string& output) -{ +int CmdCalendar::execute(std::string& output) { int rc = 0; - auto& config = Context::getContext ().config; + auto& config = Context::getContext().config; // Each month requires 28 text columns width. See how many will actually // fit. But if a preference is specified, and it fits, use it. - auto width = Context::getContext ().getWidth (); + auto width = Context::getContext().getWidth(); int preferredMonthsPerLine; - if (config.has ("calendar.monthsperline")) - preferredMonthsPerLine = config.getInteger ("calendar.monthsperline"); + if (config.has("calendar.monthsperline")) + preferredMonthsPerLine = config.getInteger("calendar.monthsperline"); else // Legacy configuration variable value - preferredMonthsPerLine = config.getInteger ("monthsperline"); + preferredMonthsPerLine = config.getInteger("monthsperline"); auto monthsThatFit = width / 26; @@ -79,78 +80,73 @@ int CmdCalendar::execute (std::string& output) monthsPerLine = preferredMonthsPerLine; // Load the pending tasks. - handleUntil (); - handleRecurrence (); - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); Datetime today; auto getPendingDate = false; auto monthsToDisplay = 1; - auto mFrom = today.month (); - auto yFrom = today.year (); + auto mFrom = today.month(); + auto yFrom = today.year(); auto mTo = mFrom; auto yTo = yFrom; // Defaults. monthsToDisplay = monthsPerLine; - mFrom = today.month (); - yFrom = today.year (); + mFrom = today.month(); + yFrom = today.year(); // Set up a vector of commands (1), for autoComplete. - std::vector commandNames {"calendar"}; + std::vector commandNames{"calendar"}; // Set up a vector of keywords, for autoComplete. - std::vector keywordNames {"due"}; + std::vector keywordNames{"due"}; // Set up a vector of months, for autoComplete. - std::vector monthNames; - for (int i = 1; i <= 12; ++i) - monthNames.push_back (Lexer::lowerCase (Datetime::monthName (i))); + std::vector monthNames; + for (int i = 1; i <= 12; ++i) monthNames.push_back(Lexer::lowerCase(Datetime::monthName(i))); // For autoComplete results. - std::vector matches; + std::vector matches; // Look at all args, regardless of sequence. auto argMonth = 0; auto argYear = 0; auto argWholeYear = false; - for (auto& arg : Context::getContext ().cli2.getWords ()) - { + for (auto& arg : Context::getContext().cli2.getWords()) { // Some version of "calendar". - if (autoComplete (Lexer::lowerCase (arg), commandNames, matches, config.getInteger ("abbreviation.minimum")) == 1) + if (autoComplete(Lexer::lowerCase(arg), commandNames, matches, + config.getInteger("abbreviation.minimum")) == 1) continue; // "due". - else if (autoComplete (Lexer::lowerCase (arg), keywordNames, matches, config.getInteger ("abbreviation.minimum")) == 1) + else if (autoComplete(Lexer::lowerCase(arg), keywordNames, matches, + config.getInteger("abbreviation.minimum")) == 1) getPendingDate = true; // "y". - else if (Lexer::lowerCase (arg) == "y") + else if (Lexer::lowerCase(arg) == "y") argWholeYear = true; // YYYY. - else if (Lexer::isAllDigits (arg) && arg.length () == 4) - argYear = strtol (arg.c_str (), nullptr, 10); + else if (Lexer::isAllDigits(arg) && arg.length() == 4) + argYear = strtol(arg.c_str(), nullptr, 10); // MM. - else if (Lexer::isAllDigits (arg) && arg.length () <= 2) - { - argMonth = strtol (arg.c_str (), nullptr, 10); - if (argMonth < 1 || argMonth > 12) - throw format ("Argument '{1}' is not a valid month.", arg); + else if (Lexer::isAllDigits(arg) && arg.length() <= 2) { + argMonth = strtol(arg.c_str(), nullptr, 10); + if (argMonth < 1 || argMonth > 12) throw format("Argument '{1}' is not a valid month.", arg); } // "January" etc. - else if (autoComplete (Lexer::lowerCase (arg), monthNames, matches, config.getInteger ("abbreviation.minimum")) == 1) - { - argMonth = Datetime::monthOfYear (matches[0]); - if (argMonth == -1) - throw format ("Argument '{1}' is not a valid month.", arg); + else if (autoComplete(Lexer::lowerCase(arg), monthNames, matches, + config.getInteger("abbreviation.minimum")) == 1) { + argMonth = Datetime::monthOfYear(matches[0]); + if (argMonth == -1) throw format("Argument '{1}' is not a valid month.", arg); } else - throw format ("Could not recognize argument '{1}'.", arg); + throw format("Could not recognize argument '{1}'.", arg); } // Supported combinations: @@ -165,58 +161,45 @@ int CmdCalendar::execute (std::string& output) // cal MM YYYY monthsPerLine arg arg false // cal MM YYYY y 12 arg arg false - if (argWholeYear || (argYear && !argMonth && !argWholeYear)) - monthsToDisplay = 12; + if (argWholeYear || (argYear && !argMonth && !argWholeYear)) monthsToDisplay = 12; if (!argMonth && argYear) mFrom = 1; else if (argMonth && argYear) mFrom = argMonth; - if (argYear) - yFrom = argYear; + if (argYear) yFrom = argYear; // Now begin the data subset and rendering. - auto countDueDates = 0; - if (getPendingDate == true) - { + if (getPendingDate == true) { // Find the oldest pending due date. - Datetime oldest (9999, 12, 31); - for (auto& task : tasks) - { - auto status = task.getStatus (); - if (status == Task::pending || status == Task::waiting) - { - if (task.has ("due") && - !task.hasTag ("nocal")) - { - ++countDueDates; - Datetime d (task.get ("due")); + Datetime oldest(9999, 12, 31); + for (auto& task : tasks) { + auto status = task.getStatus(); + if (status == Task::pending || status == Task::waiting) { + if (task.has("due") && !task.hasTag("nocal")) { + Datetime d(task.get("due")); if (d < oldest) oldest = d; } } } // Default to current month if no due date is present - if (oldest != Datetime (9999, 12, 31)) { + if (oldest != Datetime(9999, 12, 31)) { mFrom = oldest.month(); yFrom = oldest.year(); } } - if (config.getBoolean ("calendar.offset")) - { - auto moffset = config.getInteger ("calendar.offset.value") % 12; - auto yoffset = config.getInteger ("calendar.offset.value") / 12; + if (config.getBoolean("calendar.offset")) { + auto moffset = config.getInteger("calendar.offset.value") % 12; + auto yoffset = config.getInteger("calendar.offset.value") / 12; mFrom += moffset; yFrom += yoffset; - if (mFrom < 1) - { + if (mFrom < 1) { mFrom += 12; yFrom--; - } - else if (mFrom > 12) - { + } else if (mFrom > 12) { mFrom -= 12; yFrom++; } @@ -224,8 +207,7 @@ int CmdCalendar::execute (std::string& output) mTo = mFrom + monthsToDisplay - 1; yTo = yFrom; - if (mTo > 12) - { + if (mTo > 12) { mTo -= 12; yTo++; } @@ -236,15 +218,13 @@ int CmdCalendar::execute (std::string& output) std::stringstream out; out << '\n'; - while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) - { + while (yFrom < yTo || (yFrom == yTo && mFrom <= mTo)) { auto nextM = mFrom; auto nextY = yFrom; // Print month headers (cheating on the width settings, yes) - for (int i = 0 ; i < monthsPerLine ; i++) - { - auto month = Datetime::monthName (nextM); + for (int i = 0; i < monthsPerLine; i++) { + auto month = Datetime::monthName(nextM); // 12345678901234567890123456 = 26 chars wide // ^^ = center @@ -261,253 +241,202 @@ int CmdCalendar::execute (std::string& output) // +--------------------------+ auto totalWidth = 26; - auto labelWidth = month.length () + 5; // 5 = " 2009" + auto labelWidth = month.length() + 5; // 5 = " 2009" auto leftGap = (totalWidth / 2) - (labelWidth / 2); auto rightGap = totalWidth - leftGap - labelWidth; - out << std::setw (leftGap) << ' ' - << month - << ' ' - << nextY - << std::setw (rightGap) << ' '; + out << std::setw(leftGap) << ' ' << month << ' ' << nextY << std::setw(rightGap) << ' '; - if (++nextM > 12) - { + if (++nextM > 12) { nextM = 1; nextY++; } } out << '\n' - << optionalBlankLine () - << renderMonths (mFrom, yFrom, today, tasks, monthsPerLine) - << '\n'; + << optionalBlankLine() << renderMonths(mFrom, yFrom, today, tasks, monthsPerLine) << '\n'; mFrom += monthsPerLine; - if (mFrom > 12) - { + if (mFrom > 12) { mFrom -= 12; ++yFrom; } } - Color color_today (config.get ("color.calendar.today")); - Color color_due (config.get ("color.calendar.due")); - Color color_duetoday (config.get ("color.calendar.due.today")); - Color color_overdue (config.get ("color.calendar.overdue")); - Color color_weekend (config.get ("color.calendar.weekend")); - Color color_holiday (config.get ("color.calendar.holiday")); - Color color_scheduled (config.get ("color.calendar.scheduled")); - Color color_weeknumber (config.get ("color.calendar.weeknumber")); + Color color_today(config.get("color.calendar.today")); + Color color_due(config.get("color.calendar.due")); + Color color_duetoday(config.get("color.calendar.due.today")); + Color color_overdue(config.get("color.calendar.overdue")); + Color color_weekend(config.get("color.calendar.weekend")); + Color color_holiday(config.get("color.calendar.holiday")); + Color color_scheduled(config.get("color.calendar.scheduled")); + Color color_weeknumber(config.get("color.calendar.weeknumber")); - if (Context::getContext ().color () && config.getBoolean ("calendar.legend")) - { - out << "Legend: " - << color_today.colorize ("today") - << ", " - << color_weekend.colorize ("weekend") + if (Context::getContext().color() && config.getBoolean("calendar.legend")) { + out << "Legend: " << color_today.colorize("today") << ", " << color_weekend.colorize("weekend") << ", "; // If colorizing due dates, print legend - if (config.get ("calendar.details") != "none") - out << color_due.colorize ("due") - << ", " - << color_duetoday.colorize ("due-today") - << ", " - << color_overdue.colorize ("overdue") - << ", " - << color_scheduled.colorize ("scheduled") + if (config.get("calendar.details") != "none") + out << color_due.colorize("due") << ", " << color_duetoday.colorize("due-today") << ", " + << color_overdue.colorize("overdue") << ", " << color_scheduled.colorize("scheduled") << ", "; // If colorizing holidays, print legend - if (config.get ("calendar.holidays") != "none") - out << color_holiday.colorize ("holiday") << ", "; + if (config.get("calendar.holidays") != "none") out << color_holiday.colorize("holiday") << ", "; - out << color_weeknumber.colorize ("weeknumber") - << '.' - << optionalBlankLine () - << '\n'; + out << color_weeknumber.colorize("weeknumber") << '.' << optionalBlankLine() << '\n'; } - if (config.get ("calendar.details") == "full" || config.get ("calendar.holidays") == "full") - { + if (config.get("calendar.details") == "full" || config.get("calendar.holidays") == "full") { --details_mFrom; - if (details_mFrom == 0) - { + if (details_mFrom == 0) { details_mFrom = 12; --details_yFrom; } - int details_dFrom = Datetime::daysInMonth (details_yFrom, details_mFrom); + int details_dFrom = Datetime::daysInMonth(details_yFrom, details_mFrom); ++mTo; - if (mTo == 13) - { + if (mTo == 13) { mTo = 1; ++yTo; } - Datetime date_after (details_yFrom, details_mFrom, details_dFrom); - auto after = date_after.toString (config.get ("dateformat")); + Datetime date_after(details_yFrom, details_mFrom, details_dFrom); + auto after = date_after.toString(config.get("dateformat")); - Datetime date_before (yTo, mTo, 1); - auto before = date_before.toString (config.get ("dateformat")); + Datetime date_before(yTo, mTo, 1); + auto before = date_before.toString(config.get("dateformat")); // Table with due date information - if (config.get ("calendar.details") == "full") - { + if (config.get("calendar.details") == "full") { // Assert that 'report' is a valid report. - auto report = config.get ("calendar.details.report"); - if (Context::getContext ().commands.find (report) == Context::getContext ().commands.end ()) - throw std::string ("The setting 'calendar.details.report' must contain a single report name."); + auto report = config.get("calendar.details.report"); + if (Context::getContext().commands.find(report) == Context::getContext().commands.end()) + throw std::string( + "The setting 'calendar.details.report' must contain a single report name."); // TODO Fix this: cal --> task // calendar --> taskendar // If the executable was "cal" or equivalent, replace it with "task". - auto executable = Context::getContext ().cli2._original_args[0].attribute ("raw"); - auto cal = executable.find ("cal"); - if (cal != std::string::npos) - executable = executable.substr (0, cal) + PACKAGE; + auto executable = Context::getContext().cli2._original_args[0].attribute("raw"); + auto cal = executable.find("cal"); + if (cal != std::string::npos) executable = executable.substr(0, cal) + PACKAGE; - std::vector args; - args.push_back ("rc:" + Context::getContext ().rc_file._data); - args.push_back ("rc.due:0"); - args.push_back ("rc.verbose:label,affected,blank"); - if (Context::getContext ().color ()) - args.push_back ("rc._forcecolor:on"); - args.push_back ("due.after:" + after); - args.push_back ("due.before:" + before); - args.push_back ("-nocal"); - args.push_back (report); + std::vector args; + args.push_back("rc:" + Context::getContext().rc_file._data); + args.push_back("rc.due:0"); + args.push_back("rc.verbose:label,affected,blank"); + if (Context::getContext().color()) args.push_back("rc._forcecolor:on"); + args.push_back("due.after:" + after); + args.push_back("due.before:" + before); + args.push_back("-nocal"); + args.push_back(report); std::string output; - ::execute (executable, args, "", output); + ::execute(executable, args, "", output); out << output; } // Table with holiday information - if (config.get ("calendar.holidays") == "full") - { + if (config.get("calendar.holidays") == "full") { Table holTable; - holTable.width (Context::getContext ().getWidth ()); - holTable.add ("Date"); - holTable.add ("Holiday"); - setHeaderUnderline (holTable); + holTable.width(Context::getContext().getWidth()); + holTable.add("Date"); + holTable.add("Holiday"); + setHeaderUnderline(holTable); - auto dateFormat = config.get ("dateformat.holiday"); + auto dateFormat = config.get("dateformat.holiday"); - std::map > hm; // we need to store multiple holidays per day + std::map> hm; // we need to store multiple holidays per day for (auto& it : config) - if (it.first.substr (0, 8) == "holiday.") - if (it.first.substr (it.first.size () - 4) == "name") - { + if (it.first.substr(0, 8) == "holiday.") + if (it.first.substr(it.first.size() - 4) == "name") { auto holName = it.second; - auto date = config.get ("holiday." + it.first.substr (8, it.first.size () - 13) + ".date"); - auto start = config.get ("holiday." + it.first.substr (8, it.first.size () - 13) + ".start"); - auto end = config.get ("holiday." + it.first.substr (8, it.first.size () - 13) + ".end"); - if (!date.empty ()) - { - Datetime holDate (date.c_str (), dateFormat); + auto date = config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".date"); + auto start = + config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".start"); + auto end = config.get("holiday." + it.first.substr(8, it.first.size() - 13) + ".end"); + if (!date.empty()) { + Datetime holDate(date.c_str(), dateFormat); if (date_after < holDate && holDate < date_before) - hm[holDate.toEpoch()].push_back (holName); + hm[holDate.toEpoch()].push_back(holName); } - if (!start.empty () && !end.empty ()) - { - Datetime holStart (start.c_str (), dateFormat); - Datetime holEnd (end.c_str (), dateFormat); + if (!start.empty() && !end.empty()) { + Datetime holStart(start.c_str(), dateFormat); + Datetime holEnd(end.c_str(), dateFormat); if (date_after < holStart && holStart < date_before) - hm[holStart.toEpoch()].push_back ("Start of " + holName); + hm[holStart.toEpoch()].push_back("Start of " + holName); if (date_after < holEnd && holEnd < date_before) - hm[holEnd.toEpoch()].push_back ("End of " + holName); + hm[holEnd.toEpoch()].push_back("End of " + holName); } } - auto format = config.get ("report." + - config.get ("calendar.details.report") + - ".dateformat"); - if (format == "") - format = config.get ("dateformat.report"); - if (format == "") - format = config.get ("dateformat"); + auto format = config.get("report." + config.get("calendar.details.report") + ".dateformat"); + if (format == "") format = config.get("dateformat.report"); + if (format == "") format = config.get("dateformat"); - for (auto& hm_it : hm) - { + for (auto& hm_it : hm) { auto v = hm_it.second; - Datetime hDate (hm_it.first); - auto d = hDate.toString (format); - for (const auto& i : v) - { - auto row = holTable.addRow (); - holTable.set (row, 0, d); - holTable.set (row, 1, i); + Datetime hDate(hm_it.first); + auto d = hDate.toString(format); + for (const auto& i : v) { + auto row = holTable.addRow(); + holTable.set(row, 0, d); + holTable.set(row, 1, i); } } - out << optionalBlankLine () - << holTable.render () - << '\n'; + out << optionalBlankLine() << holTable.render() << '\n'; } } - output = out.str (); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -std::string CmdCalendar::renderMonths ( - int firstMonth, - int firstYear, - const Datetime& today, - std::vector & all, - int monthsPerLine) -{ - - auto& config = Context::getContext ().config; - - // What day of the week does the user consider the first? - auto weekStart = Datetime::dayOfWeek (config.get ("weekstart")); - if (weekStart != 0 && weekStart != 1) - throw std::string ("The 'weekstart' configuration variable may only contain 'Sunday' or 'Monday'."); +std::string CmdCalendar::renderMonths(int firstMonth, int firstYear, const Datetime& today, + std::vector& all, int monthsPerLine) { + auto& config = Context::getContext().config; + auto weekStart = Datetime::weekstart; // Build table for the number of months to be displayed. Table view; - setHeaderUnderline (view); - view.width (Context::getContext ().getWidth ()); - for (int i = 0 ; i < (monthsPerLine * 8); i += 8) - { - if (weekStart == 1) - { - view.add ("", false); - view.add (utf8_substr (Datetime::dayName (1), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (2), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (3), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (4), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (5), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (6), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (0), 0, 2), false); - } - else - { - view.add ("", false); - view.add (utf8_substr (Datetime::dayName (0), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (1), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (2), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (3), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (4), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (5), 0, 2), false); - view.add (utf8_substr (Datetime::dayName (6), 0, 2), false); + setHeaderUnderline(view); + view.width(Context::getContext().getWidth()); + for (int i = 0; i < (monthsPerLine * 8); i += 8) { + if (weekStart == 1) { + view.add("", false); + view.add(utf8_substr(Datetime::dayName(1), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(2), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(3), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(4), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(5), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(6), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(0), 0, 2), false); + } else { + view.add("", false); + view.add(utf8_substr(Datetime::dayName(0), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(1), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(2), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(3), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(4), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(5), 0, 2), false); + view.add(utf8_substr(Datetime::dayName(6), 0, 2), false); } } // At most, we need 6 rows. - view.addRow (); - view.addRow (); - view.addRow (); - view.addRow (); - view.addRow (); - view.addRow (); + view.addRow(); + view.addRow(); + view.addRow(); + view.addRow(); + view.addRow(); + view.addRow(); // Set number of days per month, months to render, and years to render. std::vector years; @@ -515,153 +444,126 @@ std::string CmdCalendar::renderMonths ( std::vector daysInMonth; int thisYear = firstYear; int thisMonth = firstMonth; - for (int i = 0 ; i < monthsPerLine ; i++) - { - if (thisMonth < 13) - { - years.push_back (thisYear); - } - else - { + for (int i = 0; i < monthsPerLine; i++) { + if (thisMonth < 13) { + years.push_back(thisYear); + } else { thisMonth -= 12; - years.push_back (++thisYear); + years.push_back(++thisYear); } - months.push_back (thisMonth); - daysInMonth.push_back (Datetime::daysInMonth (thisYear, thisMonth++)); + months.push_back(thisMonth); + daysInMonth.push_back(Datetime::daysInMonth(thisYear, thisMonth++)); } auto row = 0; - Color color_today (config.get ("color.calendar.today")); - Color color_due (config.get ("color.calendar.due")); - Color color_duetoday (config.get ("color.calendar.due.today")); - Color color_overdue (config.get ("color.calendar.overdue")); - Color color_weekend (config.get ("color.calendar.weekend")); - Color color_holiday (config.get ("color.calendar.holiday")); - Color color_scheduled (config.get ("color.calendar.scheduled")); - Color color_weeknumber (config.get ("color.calendar.weeknumber")); + Color color_today(config.get("color.calendar.today")); + Color color_due(config.get("color.calendar.due")); + Color color_duetoday(config.get("color.calendar.due.today")); + Color color_overdue(config.get("color.calendar.overdue")); + Color color_weekend(config.get("color.calendar.weekend")); + Color color_holiday(config.get("color.calendar.holiday")); + Color color_scheduled(config.get("color.calendar.scheduled")); + Color color_weeknumber(config.get("color.calendar.weeknumber")); // Loop through months to be added on this line. - for (int mpl = 0; mpl < monthsPerLine ; mpl++) - { + for (int mpl = 0; mpl < monthsPerLine; mpl++) { // Reset row counter for subsequent months - if (mpl != 0) - row = 0; + if (mpl != 0) row = 0; // Loop through days in month and add to table. - for (int d = 1; d <= daysInMonth[mpl]; ++d) - { - Datetime date (years[mpl], months[mpl], d); - auto dow = date.dayOfWeek (); - auto woy = date.week (); + for (int d = 1; d <= daysInMonth[mpl]; ++d) { + Datetime date(years[mpl], months[mpl], d); + auto dow = date.dayOfWeek(); + auto woy = date.week(); - if (config.getBoolean ("displayweeknumber")) - view.set (row, - (8 * mpl), - // Make sure the week number is always 4 columns, space-padded. - format ((woy < 10 ? " {1}" : " {1}"), woy), - color_weeknumber); + if (config.getBoolean("displayweeknumber")) + view.set(row, (8 * mpl), + // Make sure the week number is always 4 columns, space-padded. + format((woy < 10 ? " {1}" : " {1}"), woy), color_weeknumber); // Calculate column id. auto thisCol = dow + // 0 = Sunday (weekStart == 1 ? 0 : 1) + // Offset for weekStart (8 * mpl); // Columns in 1 month - if (thisCol == (8 * mpl)) - thisCol += 7; + if (thisCol == (8 * mpl)) thisCol += 7; - view.set (row, thisCol, d); + view.set(row, thisCol, d); - if (Context::getContext ().color ()) - { + if (Context::getContext().color()) { Color cellColor; // colorize weekends - if (dow == 0 || dow == 6) - cellColor.blend (color_weekend); + if (dow == 0 || dow == 6) cellColor.blend(color_weekend); // colorize holidays - if (config.get ("calendar.holidays") != "none") - { - auto dateFormat = config.get ("dateformat.holiday"); - for (auto& hol : config) - { - if (hol.first.substr (0, 8) == "holiday.") - { - if (hol.first.substr (hol.first.size () - 4) == "date") - { + if (config.get("calendar.holidays") != "none") { + auto dateFormat = config.get("dateformat.holiday"); + for (auto& hol : config) { + if (hol.first.substr(0, 8) == "holiday.") { + if (hol.first.substr(hol.first.size() - 4) == "date") { auto value = hol.second; - Datetime holDate (value.c_str (), dateFormat); - if (holDate.sameDay (date)) - cellColor.blend (color_holiday); + Datetime holDate(value.c_str(), dateFormat); + if (holDate.sameDay(date)) cellColor.blend(color_holiday); } - if (hol.first.substr (hol.first.size () - 5) == "start" && - config.has ("holiday." + hol.first.substr (8, hol.first.size () - 14) + ".end")) - { + if (hol.first.substr(hol.first.size() - 5) == "start" && + config.has("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end")) { auto start = hol.second; - auto end = config.get ("holiday." + hol.first.substr (8, hol.first.size () - 14) + ".end"); - Datetime holStart (start.c_str (), dateFormat); - Datetime holEnd (end.c_str (), dateFormat); - if (holStart <= date && date <= holEnd) - cellColor.blend (color_holiday); + auto end = + config.get("holiday." + hol.first.substr(8, hol.first.size() - 14) + ".end"); + Datetime holStart(start.c_str(), dateFormat); + Datetime holEnd(end.c_str(), dateFormat); + if (holStart <= date && date <= holEnd) cellColor.blend(color_holiday); } } } } // colorize today - if (today.sameDay (date)) - cellColor.blend (color_today); + if (today.sameDay(date)) cellColor.blend(color_today); // colorize due and scheduled tasks - if (config.get ("calendar.details") != "none") - { - config.set ("due", 0); - config.set ("scheduled", 0); + if (config.get("calendar.details") != "none") { + config.set("due", 0); + config.set("scheduled", 0); // if a date has a task that is due on that day, the due color // takes precedence over the scheduled color bool coloredWithDue = false; - for (auto& task : all) - { - auto status = task.getStatus (); - if ((status == Task::pending || - status == Task::waiting ) && - !task.hasTag ("nocal")) - { - if(task.has("scheduled") && !coloredWithDue) { - std::string scheduled = task.get ("scheduled"); - Datetime scheduleddmy (strtoll (scheduled.c_str(), nullptr, 10)); + for (auto& task : all) { + auto status = task.getStatus(); + if ((status == Task::pending || status == Task::waiting) && !task.hasTag("nocal")) { + if (task.has("scheduled") && !coloredWithDue) { + std::string scheduled = task.get("scheduled"); + Datetime scheduleddmy(strtoll(scheduled.c_str(), nullptr, 10)); - if (scheduleddmy.sameDay (date)) - { + if (scheduleddmy.sameDay(date)) { cellColor.blend(color_scheduled); } } - if(task.has("due")) { - std::string due = task.get ("due"); - Datetime duedmy (strtoll (due.c_str(), nullptr, 10)); + if (task.has("due")) { + std::string due = task.get("due"); + Datetime duedmy(strtoll(due.c_str(), nullptr, 10)); - if (duedmy.sameDay (date)) - { + if (duedmy.sameDay(date)) { coloredWithDue = true; - switch (task.getDateState ("due")) - { - case Task::dateNotDue: - break; + switch (task.getDateState("due")) { + case Task::dateNotDue: + break; - case Task::dateAfterToday: - cellColor.blend (color_due); - break; + case Task::dateAfterToday: + cellColor.blend(color_due); + break; - case Task::dateLaterToday: - cellColor.blend (color_duetoday); - break; + case Task::dateLaterToday: + cellColor.blend(color_duetoday); + break; - case Task::dateEarlierToday: - case Task::dateBeforeToday: - cellColor.blend (color_overdue); - break; + case Task::dateEarlierToday: + case Task::dateBeforeToday: + cellColor.blend(color_overdue); + break; } } } @@ -669,19 +571,17 @@ std::string CmdCalendar::renderMonths ( } } - view.set (row, thisCol, cellColor); + view.set(row, thisCol, cellColor); } // Check for end of week, and... int eow = 6; - if (weekStart == 1) - eow = 0; - if (dow == eow && d < daysInMonth[mpl]) - row++; + if (weekStart == 1) eow = 0; + if (dow == eow && d < daysInMonth[mpl]) row++; } } - return view.render (); + return view.render(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdCalendar.h b/src/commands/CmdCalendar.h index d2645ef3b..afd1f9d27 100644 --- a/src/commands/CmdCalendar.h +++ b/src/commands/CmdCalendar.h @@ -27,20 +27,20 @@ #ifndef INCLUDED_CMDCALENDAR #define INCLUDED_CMDCALENDAR -#include -#include +#include #include #include -#include -class CmdCalendar : public Command -{ -public: - CmdCalendar (); - int execute (std::string&); +#include +#include -private: - std::string renderMonths (int, int, const Datetime&, std::vector &, int); +class CmdCalendar : public Command { + public: + CmdCalendar(); + int execute(std::string&); + + private: + std::string renderMonths(int, int, const Datetime&, std::vector&, int); }; #endif diff --git a/src/commands/CmdColor.cpp b/src/commands/CmdColor.cpp index 0721edc1e..da17b9436 100644 --- a/src/commands/CmdColor.cpp +++ b/src/commands/CmdColor.cpp @@ -25,162 +25,140 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include #include +#include +#include #include #include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdColor::CmdColor () -{ - _keyword = "colors"; - _usage = "task colors [sample | legend]"; - _description = "All colors, a sample, or a legend"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdColor::CmdColor() { + _keyword = "colors"; + _usage = "task colors [sample | legend]"; + _description = "All colors, a sample, or a legend"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdColor::execute (std::string& output) -{ +int CmdColor::execute(std::string& output) { int rc = 0; // Get the non-attribute, non-fancy command line arguments. auto legend = false; - auto words = Context::getContext ().cli2.getWords (); + auto words = Context::getContext().cli2.getWords(); for (auto& word : words) - if (closeEnough ("legend", word)) - legend = true; + if (closeEnough("legend", word)) legend = true; std::stringstream out; - if (Context::getContext ().color ()) - { + if (Context::getContext().color()) { // If the description contains 'legend', show all the colors currently in // use. - if (legend) - { + if (legend) { out << "\nHere are the colors currently in use:\n"; Table view; - view.width (Context::getContext ().getWidth ()); - if (Context::getContext ().config.getBoolean ("color")) - view.forceColor (); - view.add ("Color"); - view.add ("Definition"); + view.width(Context::getContext().getWidth()); + if (Context::getContext().config.getBoolean("color")) view.forceColor(); + view.add("Color"); + view.add("Definition"); - for (auto& item : Context::getContext ().config) - { + for (auto& item : Context::getContext().config) { // Skip items with 'color' in their name, that are not referring to // actual colors. - if (item.first != "_forcecolor" && - item.first != "color" && - item.first.find ("color") == 0) - { - Color color (Context::getContext ().config.get (item.first)); - int row = view.addRow (); - view.set (row, 0, item.first, color); - view.set (row, 1, item.second, color); + if (item.first != "_forcecolor" && item.first != "color" && item.first.find("color") == 0) { + Color color(Context::getContext().config.get(item.first)); + int row = view.addRow(); + view.set(row, 0, item.first, color); + view.set(row, 1, item.second, color); } } - out << view.render () - << '\n'; + out << view.render() << '\n'; } // If there is something in the description, then assume that is a color, // and display it as a sample. - else if (words.size ()) - { - Color one ("black on bright yellow"); - Color two ("underline cyan on bright blue"); - Color three ("color214 on color202"); - Color four ("rgb150 on rgb020"); - Color five ("underline grey10 on grey3"); - Color six ("red on color173"); + else if (words.size()) { + Color one("black on bright yellow"); + Color two("underline cyan on bright blue"); + Color three("color214 on color202"); + Color four("rgb150 on rgb020"); + Color five("underline grey10 on grey3"); + Color six("red on color173"); std::string swatch; - for (auto word = words.begin (); word != words.end (); ++word) - { - if (word != words.begin ()) - swatch += ' '; + for (auto word = words.begin(); word != words.end(); ++word) { + if (word != words.begin()) swatch += ' '; swatch += *word; } - Color sample (swatch); + Color sample(swatch); out << '\n' << "Use this command to see how colors are displayed by your terminal.\n" << "\n\n" << "16-color usage (supports underline, bold text, bright background):\n" - << " " << one.colorize ("task color black on bright yellow") << '\n' - << " " << two.colorize ("task color underline cyan on bright blue") << '\n' + << " " << one.colorize("task color black on bright yellow") << '\n' + << " " << two.colorize("task color underline cyan on bright blue") << '\n' << '\n' << "256-color usage (supports underline):\n" - << " " << three.colorize ("task color color214 on color202") << '\n' - << " " << four.colorize ("task color rgb150 on rgb020") << '\n' - << " " << five.colorize ("task color underline grey10 on grey3") << '\n' - << " " << six.colorize ("task color red on color173") << '\n' + << " " << three.colorize("task color color214 on color202") << '\n' + << " " << four.colorize("task color rgb150 on rgb020") << '\n' + << " " << five.colorize("task color underline grey10 on grey3") << '\n' + << " " << six.colorize("task color red on color173") << '\n' << '\n' << "Your sample:\n\n" - << " " << sample.colorize ("task color " + swatch) << "\n\n"; + << " " << sample.colorize("task color " + swatch) << "\n\n"; } // Show all supported colors. Possibly show some unsupported ones too. - else - { + else { out << '\n' << "Basic colors\n" - << ' ' << Color::colorize (" black ", "black") - << ' ' << Color::colorize (" red ", "red") - << ' ' << Color::colorize (" blue ", "blue") - << ' ' << Color::colorize (" green ", "green") - << ' ' << Color::colorize (" magenta ", "magenta") - << ' ' << Color::colorize (" cyan ", "cyan") - << ' ' << Color::colorize (" yellow ", "yellow") - << ' ' << Color::colorize (" white ", "white") - << '\n' - << ' ' << Color::colorize (" black ", "white on black") - << ' ' << Color::colorize (" red ", "white on red") - << ' ' << Color::colorize (" blue ", "white on blue") - << ' ' << Color::colorize (" green ", "black on green") - << ' ' << Color::colorize (" magenta ", "black on magenta") - << ' ' << Color::colorize (" cyan ", "black on cyan") - << ' ' << Color::colorize (" yellow ", "black on yellow") - << ' ' << Color::colorize (" white ", "black on white") - << "\n\n"; + << ' ' << Color::colorize(" black ", "black") << ' ' << Color::colorize(" red ", "red") + << ' ' << Color::colorize(" blue ", "blue") << ' ' << Color::colorize(" green ", "green") + << ' ' << Color::colorize(" magenta ", "magenta") << ' ' + << Color::colorize(" cyan ", "cyan") << ' ' << Color::colorize(" yellow ", "yellow") + << ' ' << Color::colorize(" white ", "white") << '\n' + << ' ' << Color::colorize(" black ", "white on black") << ' ' + << Color::colorize(" red ", "white on red") << ' ' + << Color::colorize(" blue ", "white on blue") << ' ' + << Color::colorize(" green ", "black on green") << ' ' + << Color::colorize(" magenta ", "black on magenta") << ' ' + << Color::colorize(" cyan ", "black on cyan") << ' ' + << Color::colorize(" yellow ", "black on yellow") << ' ' + << Color::colorize(" white ", "black on white") << "\n\n"; out << "Effects\n" - << ' ' << Color::colorize (" red ", "red") - << ' ' << Color::colorize (" bold red ", "bold red") - << ' ' << Color::colorize (" underline on blue ", "underline on blue") - << ' ' << Color::colorize (" on green ", "black on green") - << ' ' << Color::colorize (" on bright green ", "black on bright green") - << ' ' << Color::colorize (" inverse ", "inverse") - << "\n\n"; + << ' ' << Color::colorize(" red ", "red") << ' ' + << Color::colorize(" bold red ", "bold red") << ' ' + << Color::colorize(" underline on blue ", "underline on blue") << ' ' + << Color::colorize(" on green ", "black on green") << ' ' + << Color::colorize(" on bright green ", "black on bright green") << ' ' + << Color::colorize(" inverse ", "inverse") << "\n\n"; // 16 system colors. - out << "color0 - color15" - << '\n' - << " 0 1 2 . . .\n"; - for (int r = 0; r < 2; ++r) - { + out << "color0 - color15" << '\n' << " 0 1 2 . . .\n"; + for (int r = 0; r < 2; ++r) { out << " "; - for (int c = 0; c < 8; ++c) - { + for (int c = 0; c < 8; ++c) { std::stringstream s; - s << "on color" << (r*8 + c); - out << Color::colorize (" ", s.str ()); + s << "on color" << (r * 8 + c); + out << Color::colorize(" ", s.str()); } out << '\n'; @@ -189,43 +167,40 @@ int CmdColor::execute (std::string& output) out << " . . . 15\n\n"; // Color cube. - out << "Color cube rgb" - << Color::colorize ("0", "bold red") - << Color::colorize ("0", "bold green") - << Color::colorize ("0", "bold blue") - << " - rgb" - << Color::colorize ("5", "bold red") - << Color::colorize ("5", "bold green") - << Color::colorize ("5", "bold blue") - << " (also color16 - color231)" + out << "Color cube rgb" << Color::colorize("0", "bold red") + << Color::colorize("0", "bold green") << Color::colorize("0", "bold blue") << " - rgb" + << Color::colorize("5", "bold red") << Color::colorize("5", "bold green") + << Color::colorize("5", "bold blue") << " (also color16 - color231)" << '\n' + << " " + << Color::colorize( + "0 " + "1 " + "2 " + "3 " + "4 " + "5", + "bold red") << '\n' - << " " << Color::colorize ("0 " - "1 " - "2 " - "3 " - "4 " - "5", "bold red") - << '\n' - << " " << Color::colorize ("0 1 2 3 4 5 " - "0 1 2 3 4 5 " - "0 1 2 3 4 5 " - "0 1 2 3 4 5 " - "0 1 2 3 4 5 " - "0 1 2 3 4 5", "bold blue") + << " " + << Color::colorize( + "0 1 2 3 4 5 " + "0 1 2 3 4 5 " + "0 1 2 3 4 5 " + "0 1 2 3 4 5 " + "0 1 2 3 4 5 " + "0 1 2 3 4 5", + "bold blue") << '\n'; - char label [12]; - for (int g = 0; g < 6; ++g) - { - snprintf (label, 12, " %d", g); - out << Color::colorize (label, "bold green"); - for (int r = 0; r < 6; ++r) - { - for (int b = 0; b < 6; ++b) - { + char label[12]; + for (int g = 0; g < 6; ++g) { + snprintf(label, 12, " %d", g); + out << Color::colorize(label, "bold green"); + for (int r = 0; r < 6; ++r) { + for (int b = 0; b < 6; ++b) { std::stringstream s; s << "on rgb" << r << g << b; - out << Color::colorize (" ", s.str ()); + out << Color::colorize(" ", s.str()); } out << ' '; @@ -240,25 +215,23 @@ int CmdColor::execute (std::string& output) out << "Gray ramp gray0 - gray23 (also color232 - color255)\n" << " 0 1 2 . . . . . . 23\n" << " "; - for (int g = 0; g < 24; ++g) - { + for (int g = 0; g < 24; ++g) { std::stringstream s; s << "on gray" << g; - out << Color::colorize (" ", s.str ()); + out << Color::colorize(" ", s.str()); } out << "\n\n" << "Try running 'task color white on red'.\n" << '\n'; } - } - else - { - out << "Color is currently turned off in your .taskrc file. To enable color, remove the line 'color=off', or change the 'off' to 'on'.\n"; + } else { + out << "Color is currently turned off in your .taskrc file. To enable color, remove the line " + "'color=off', or change the 'off' to 'on'.\n"; rc = 1; } - output = out.str (); + output = out.str(); return rc; } diff --git a/src/commands/CmdColor.h b/src/commands/CmdColor.h index 34c6cd674..785de4a74 100644 --- a/src/commands/CmdColor.h +++ b/src/commands/CmdColor.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDCOLOR #define INCLUDED_CMDCOLOR -#include #include -class CmdColor : public Command -{ -public: - CmdColor (); - int execute (std::string&); +#include + +class CmdColor : public Command { + public: + CmdColor(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdColumns.cpp b/src/commands/CmdColumns.cpp index d85b23fda..4a56e5d0f 100644 --- a/src/commands/CmdColumns.cpp +++ b/src/commands/CmdColumns.cpp @@ -25,124 +25,118 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include +#include #include #include -#include #include #include -#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdColumns::CmdColumns () -{ - _keyword = "columns"; - _usage = "task columns [substring]"; - _description = "All supported columns and formatting styles"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdColumns::CmdColumns() { + _keyword = "columns"; + _usage = "task columns [substring]"; + _description = "All supported columns and formatting styles"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::config; + _category = Command::Category::config; } //////////////////////////////////////////////////////////////////////////////// -int CmdColumns::execute (std::string& output) -{ +int CmdColumns::execute(std::string& output) { // Obtain the arguments from the description. That way, things like '--' // have already been handled. - auto words = Context::getContext ().cli2.getWords (); - if (words.size () > 1) - throw std::string ("You can only specify one search string."); + auto words = Context::getContext().cli2.getWords(); + if (words.size() > 1) throw std::string("You can only specify one search string."); // Include all columns in the table. - std::vector names; - for (const auto& col : Context::getContext ().columns) - names.push_back (col.first); + std::vector names; + for (const auto& col : Context::getContext().columns) names.push_back(col.first); - std::sort (names.begin (), names.end ()); + std::sort(names.begin(), names.end()); // Render a list of column names, formats and examples. Table formats; - formats.width (Context::getContext ().getWidth ()); - formats.add ("Columns"); - formats.add ("Type"); - formats.add ("Modifiable"); - formats.add ("Supported Formats"); - formats.add ("Example"); - setHeaderUnderline (formats); + formats.width(Context::getContext().getWidth()); + formats.add("Columns"); + formats.add("Type"); + formats.add("Modifiable"); + formats.add("Supported Formats"); + formats.add("Example"); + setHeaderUnderline(formats); - for (const auto& name : names) - { - if (words.size () == 0 || - find (name, words[0], false) != std::string::npos) - { - auto styles = Context::getContext ().columns[name]->styles (); - auto examples = Context::getContext ().columns[name]->examples (); + for (const auto& name : names) { + if (words.size() == 0 || find(name, words[0], false) != std::string::npos) { + auto styles = Context::getContext().columns[name]->styles(); + auto examples = Context::getContext().columns[name]->examples(); - for (unsigned int i = 0; i < styles.size (); ++i) - { - auto row = formats.addRow (); - formats.set (row, 0, i == 0 ? name : ""); - formats.set (row, 1, i == 0 ? Context::getContext ().columns[name]->type () : ""); - formats.set (row, 2, i == 0 ? (Context::getContext ().columns[name]->modifiable () ? "Modifiable" : "Read Only") : ""); - formats.set (row, 3, styles[i] + (i == 0 ? "*" : "")); - formats.set (row, 4, i < examples.size () ? examples[i] : ""); + for (unsigned int i = 0; i < styles.size(); ++i) { + auto row = formats.addRow(); + formats.set(row, 0, i == 0 ? name : ""); + formats.set(row, 1, i == 0 ? Context::getContext().columns[name]->type() : ""); + formats.set(row, 2, + i == 0 ? (Context::getContext().columns[name]->modifiable() ? "Modifiable" + : "Read Only") + : ""); + formats.set(row, 3, styles[i] + (i == 0 ? "*" : "")); + formats.set(row, 4, i < examples.size() ? examples[i] : ""); } } } - auto row = formats.addRow (); - formats.set (row, 0, ""); - formats.set (row, 1, ""); - formats.set (row, 2, "Modifiable"); - formats.set (row, 3, "default*"); + auto row = formats.addRow(); + formats.set(row, 0, ""); + formats.set(row, 1, ""); + formats.set(row, 2, "Modifiable"); + formats.set(row, 3, "default*"); - row = formats.addRow (); - formats.set (row, 0, ""); - formats.set (row, 3, "indicator"); + row = formats.addRow(); + formats.set(row, 0, ""); + formats.set(row, 3, "indicator"); - output = optionalBlankLine () - + formats.render () - + '\n' - + "* Means default format, and therefore optional. For example, 'due' and 'due.formatted' are equivalent.\n"; + output = optionalBlankLine() + formats.render() + '\n' + + "* Means default format, and therefore optional. For example, 'due' and " + "'due.formatted' are equivalent.\n"; return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionColumns::CmdCompletionColumns () -{ - _keyword = "_columns"; - _usage = "task _columns"; - _description = "Displays only a list of supported columns"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionColumns::CmdCompletionColumns() { + _keyword = "_columns"; + _usage = "task _columns"; + _description = "Displays only a list of supported columns"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionColumns::execute (std::string& output) -{ +int CmdCompletionColumns::execute(std::string& output) { // Include all columns. - std::vector names; - for (const auto& col : Context::getContext ().columns) - names.push_back (col.first); + std::vector names; + for (const auto& col : Context::getContext().columns) names.push_back(col.first); - std::sort (names.begin (), names.end ()); + std::sort(names.begin(), names.end()); // Render only the column names. - for (const auto& name : names) - output += name + '\n'; + for (const auto& name : names) output += name + '\n'; return 0; } diff --git a/src/commands/CmdColumns.h b/src/commands/CmdColumns.h index 9d4d0cedc..7a12caf13 100644 --- a/src/commands/CmdColumns.h +++ b/src/commands/CmdColumns.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDCOLUMNS #define INCLUDED_CMDCOLUMNS -#include #include -class CmdColumns : public Command -{ -public: - CmdColumns (); - int execute (std::string&); +#include + +class CmdColumns : public Command { + public: + CmdColumns(); + int execute(std::string&); }; -class CmdCompletionColumns : public Command -{ -public: - CmdCompletionColumns (); - int execute (std::string&); +class CmdCompletionColumns : public Command { + public: + CmdCompletionColumns(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdCommands.cpp b/src/commands/CmdCommands.cpp index b7a91ee2e..6fb44876d 100644 --- a/src/commands/CmdCommands.cpp +++ b/src/commands/CmdCommands.cpp @@ -25,146 +25,135 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include +#include #include #include -#include +#include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdCommands::CmdCommands () -{ - _keyword = "commands"; - _usage = "task commands"; - _description = "Generates a list of all commands, with behavior details"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCommands::CmdCommands() { + _keyword = "commands"; + _usage = "task commands"; + _description = "Generates a list of all commands, with behavior details"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdCommands::execute (std::string& output) -{ +int CmdCommands::execute(std::string& output) { Table view; - view.width (Context::getContext ().getWidth ()); - view.add ("Command"); - view.add ("Category"); - view.add ("R/W", false); - view.add ("ID", false); - view.add ("GC", false); - view.add ("Context", false); - view.add ("Filter", false); - view.add ("Mods", false); - view.add ("Misc", false); - view.add ("Description"); - view.leftMargin (Context::getContext ().config.getInteger ("indent.report")); - view.extraPadding (Context::getContext ().config.getInteger ("row.padding")); - view.intraPadding (Context::getContext ().config.getInteger ("column.padding")); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.add("Command"); + view.add("Category"); + view.add("R/W", false); + view.add("ID", false); + view.add("GC", false); + view.add("Recur", false); + view.add("Context", false); + view.add("Filter", false); + view.add("Mods", false); + view.add("Misc", false); + view.add("Description"); + view.leftMargin(Context::getContext().config.getInteger("indent.report")); + view.extraPadding(Context::getContext().config.getInteger("row.padding")); + view.intraPadding(Context::getContext().config.getInteger("column.padding")); + setHeaderUnderline(view); - for (auto& command : Context::getContext ().commands) - { - auto row = view.addRow (); - view.set (row, 0, command.first); - view.set (row, 1, Command::categoryNames.at (command.second->category ())); + for (auto& command : Context::getContext().commands) { + auto row = view.addRow(); + view.set(row, 0, command.first); + view.set(row, 1, Command::categoryNames.at(command.second->category())); - if (command.second->read_only ()) - view.set (row, 2, "RO"); + if (command.second->read_only()) + view.set(row, 2, "RO"); else - view.set (row, 2, "RW"); + view.set(row, 2, "RW"); - if (command.second->displays_id ()) - view.set (row, 3, "ID"); + if (command.second->displays_id()) view.set(row, 3, "ID"); - if (command.second->needs_gc ()) - view.set (row, 4, "GC"); + if (command.second->needs_gc()) view.set(row, 4, "GC"); - if (command.second->uses_context ()) - view.set (row, 5, "Ctxt"); + if (command.second->needs_recur_update()) view.set(row, 5, "Recur"); - if (command.second->accepts_filter ()) - view.set (row, 6, "Filt"); + if (command.second->uses_context()) view.set(row, 6, "Ctxt"); - if (command.second->accepts_modifications ()) - view.set (row, 7, "Mods"); + if (command.second->accepts_filter()) view.set(row, 7, "Filt"); - if (command.second->accepts_miscellaneous ()) - view.set (row, 8, "Misc"); + if (command.second->accepts_modifications()) view.set(row, 8, "Mods"); - view.set (row, 9, command.second->description ()); + if (command.second->accepts_miscellaneous()) view.set(row, 9, "Misc"); + + view.set(row, 10, command.second->description()); } - output = optionalBlankLine () - + view.render () - + optionalBlankLine () - + '\n'; + output = optionalBlankLine() + view.render() + optionalBlankLine() + '\n'; return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionCommands::CmdCompletionCommands () -{ - _keyword = "_commands"; - _usage = "task _commands"; - _description = "Generates a list of all commands, for autocompletion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionCommands::CmdCompletionCommands() { + _keyword = "_commands"; + _usage = "task _commands"; + _description = "Generates a list of all commands, for autocompletion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionCommands::execute (std::string& output) -{ +int CmdCompletionCommands::execute(std::string& output) { // Get a list of all commands. - std::vector commands; - for (const auto& command : Context::getContext ().commands) - commands.push_back (command.first); + std::vector commands; + for (const auto& command : Context::getContext().commands) commands.push_back(command.first); // Sort alphabetically. - std::sort (commands.begin (), commands.end ()); + std::sort(commands.begin(), commands.end()); std::stringstream out; - for (const auto& c : commands) - out << c << '\n'; + for (const auto& c : commands) out << c << '\n'; - output = out.str (); + output = out.str(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdZshCommands::CmdZshCommands () -{ - _keyword = "_zshcommands"; - _usage = "task _zshcommands"; - _description = "Generates a list of all commands, for zsh autocompletion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdZshCommands::CmdZshCommands() { + _keyword = "_zshcommands"; + _usage = "task _zshcommands"; + _description = "Generates a list of all commands, for zsh autocompletion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -struct ZshCommand -{ - bool operator< (const struct ZshCommand&) const; +struct ZshCommand { + bool operator<(const struct ZshCommand&) const; Command::Category _category; std::string _command; @@ -172,49 +161,40 @@ struct ZshCommand }; //////////////////////////////////////////////////////////////////////////////// -bool ZshCommand::operator< (const struct ZshCommand& other) const -{ +bool ZshCommand::operator<(const struct ZshCommand& other) const { // Lexicographical comparison. - if (_category != other._category) - return (_category < other._category); + if (_category != other._category) return (_category < other._category); - if (_command != other._command) - return (_command < other._command); + if (_command != other._command) return (_command < other._command); - if (_description != other._description) - return (_description < other._description); + if (_description != other._description) return (_description < other._description); return false; } //////////////////////////////////////////////////////////////////////////////// -int CmdZshCommands::execute (std::string& output) -{ +int CmdZshCommands::execute(std::string& output) { // Get a list of all command descriptions, sorted by category and then // alphabetically by command name. // Since not all supported compilers support tuples, we use a least common // denominator alternative: a custom struct type. - std::vector commands; - for (auto& command : Context::getContext ().commands) - { - ZshCommand zshCommand {command.second->category (), - command.first, - command.second->description ()}; - commands.push_back (zshCommand); + std::vector commands; + for (auto& command : Context::getContext().commands) { + ZshCommand zshCommand{command.second->category(), command.first, command.second->description()}; + commands.push_back(zshCommand); } - std::sort (commands.begin (), commands.end ()); + std::sort(commands.begin(), commands.end()); // Emit the commands in order. std::stringstream out; for (const auto& zc : commands) - out << zc._command << ':' - << Command::categoryNames.at (zc._category) << ':' - << zc._description << '\n'; + out << zc._command << ':' << Command::categoryNames.at(zc._category) << ':' << zc._description + << '\n'; - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdCommands.h b/src/commands/CmdCommands.h index eb6e45000..59c936717 100644 --- a/src/commands/CmdCommands.h +++ b/src/commands/CmdCommands.h @@ -27,28 +27,26 @@ #ifndef INCLUDED_CMDCOMMANDS #define INCLUDED_CMDCOMMANDS -#include #include -class CmdCommands : public Command -{ -public: - CmdCommands (); - int execute (std::string&); +#include + +class CmdCommands : public Command { + public: + CmdCommands(); + int execute(std::string&); }; -class CmdCompletionCommands : public Command -{ -public: - CmdCompletionCommands (); - int execute (std::string&); +class CmdCompletionCommands : public Command { + public: + CmdCompletionCommands(); + int execute(std::string&); }; -class CmdZshCommands : public Command -{ -public: - CmdZshCommands (); - int execute (std::string&); +class CmdZshCommands : public Command { + public: + CmdZshCommands(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdConfig.cpp b/src/commands/CmdConfig.cpp index 6f5eab75a..99270f033 100644 --- a/src/commands/CmdConfig.cpp +++ b/src/commands/CmdConfig.cpp @@ -25,64 +25,61 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include #include -#include #include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdConfig::CmdConfig () -{ - _keyword = "config"; - _usage = "task config [name [value | '']]"; - _description = "Change settings in the task configuration"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdConfig::CmdConfig() { + _keyword = "config"; + _usage = "task config [name [value | '']]"; + _description = "Change settings in the task configuration"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::config; + _category = Command::Category::config; } //////////////////////////////////////////////////////////////////////////////// -bool CmdConfig::setConfigVariable ( - const std::string& name, - const std::string& value, - bool confirmation /* = false */) -{ +bool CmdConfig::setConfigVariable(const std::string& name, const std::string& value, + bool confirmation /* = false */) { // Read .taskrc (or equivalent) - std::vector contents; - File::read (Context::getContext ().config.file (), contents); + std::vector contents; + File::read(Context::getContext().config.file(), contents); auto found = false; auto change = false; - for (auto& line : contents) - { + for (auto& line : contents) { // Get l-trimmed version of the line - auto trimmed_line = trim (line, " "); + auto trimmed_line = trim(line, " "); // If there is a comment on the line, it must follow the pattern. - auto comment = line.find ('#'); - auto pos = trimmed_line.find (name + '='); + auto comment = line.find('#'); + auto pos = trimmed_line.find(name + '='); // TODO: Use std::regex here - if (pos == 0) - { + if (pos == 0) { found = true; if (!confirmation || - confirm (format ("Are you sure you want to change the value of '{1}' from '{2}' to '{3}'?", name, Context::getContext ().config.get (name), value))) - { - auto new_line = line.substr (0, pos + name.length () + 1) + json::encode (value); + confirm(format("Are you sure you want to change the value of '{1}' from '{2}' to '{3}'?", + name, Context::getContext().config.get(name), value))) { + auto new_line = line.substr(0, pos + name.length() + 1) + json::encode(value); // Preserve the comment - if (comment != std::string::npos) - new_line += " " + line.substr (comment); + if (comment != std::string::npos) new_line += " " + line.substr(comment); // Rewrite the line line = new_line; @@ -92,61 +89,56 @@ bool CmdConfig::setConfigVariable ( } // Not found, so append instead. - if (! found && - (! confirmation || - confirm (format ("Are you sure you want to add '{1}' with a value of '{2}'?", name, value)))) - { - contents.push_back (name + '=' + json::encode (value)); + if (!found && + (!confirmation || + confirm(format("Are you sure you want to add '{1}' with a value of '{2}'?", name, value)))) { + contents.push_back(name + '=' + json::encode(value)); change = true; } if (change) - File::write (Context::getContext ().config.file (), contents); + if (!File::write(Context::getContext().config.file(), contents)) + throw format("Could not write to '{1}'.", Context::getContext().config.file()); return change; } //////////////////////////////////////////////////////////////////////////////// -int CmdConfig::unsetConfigVariable (const std::string& name, bool confirmation /* = false */) -{ +int CmdConfig::unsetConfigVariable(const std::string& name, bool confirmation /* = false */) { // Read .taskrc (or equivalent) - std::vector contents; - File::read (Context::getContext ().config.file (), contents); + std::vector contents; + File::read(Context::getContext().config.file(), contents); auto found = false; auto change = false; - for (auto line = contents.begin (); line != contents.end (); ) - { + for (auto line = contents.begin(); line != contents.end();) { auto lineDeleted = false; // Get l-trimmed version of the line // If there is a comment on the line, it must follow the pattern. - auto pos = trim (*line, " ").find (name + '='); + auto pos = trim(*line, " ").find(name + '='); // TODO: Use std::regex here - if (pos == 0) - { + if (pos == 0) { found = true; // Remove name - if (!confirmation || - confirm (format ("Are you sure you want to remove '{1}'?", name))) - { + if (!confirmation || confirm(format("Are you sure you want to remove '{1}'?", name))) { // vector::erase method returns a valid iterator to the next object - line = contents.erase (line); + line = contents.erase(line); lineDeleted = true; change = true; } } - if (! lineDeleted) - line++; + if (!lineDeleted) line++; } if (change) - File::write (Context::getContext ().config.file (), contents); + if (!File::write(Context::getContext().config.file(), contents)) + throw format("Could not write to '{1}'.", Context::getContext().config.file()); if (change && found) return 0; @@ -157,107 +149,89 @@ int CmdConfig::unsetConfigVariable (const std::string& name, bool confirmation / } //////////////////////////////////////////////////////////////////////////////// -int CmdConfig::execute (std::string& output) -{ +int CmdConfig::execute(std::string& output) { auto rc = 0; std::stringstream out; // Get the non-attribute, non-fancy command line arguments. - std::vector words = Context::getContext ().cli2.getWords (); + std::vector words = Context::getContext().cli2.getWords(); // Support: // task config name value # set name to value // task config name "" # set name to blank // task config name # remove name - if (words.size ()) - { - auto confirmation = Context::getContext ().config.getBoolean ("confirmation"); + if (words.size()) { + auto confirmation = Context::getContext().config.getBoolean("confirmation"); auto found = false; auto name = words[0]; std::string value = ""; // Join the remaining words into config variable's value - if (words.size () > 1) - { - for (unsigned int i = 1; i < words.size (); ++i) - { - if (i > 1) - value += ' '; + if (words.size() > 1) { + for (unsigned int i = 1; i < words.size(); ++i) { + if (i > 1) value += ' '; value += words[i]; } } - if (name != "") - { + if (name != "") { auto change = false; // task config name value // task config name "" - if (words.size () > 1) - change = setConfigVariable(name, value, confirmation); + if (words.size() > 1) change = setConfigVariable(name, value, confirmation); // task config name - else - { + else { rc = unsetConfigVariable(name, confirmation); - if (rc == 0) - { + if (rc == 0) { change = true; found = true; - } - else if (rc == 1) + } else if (rc == 1) found = true; - if (! found) - throw format ("No entry named '{1}' found.", name); + if (!found) throw format("No entry named '{1}' found.", name); } // Show feedback depending on whether .taskrc has been rewritten - if (change) - { - out << format ("Config file {1} modified.", Context::getContext ().config.file ()) - << '\n'; - } - else + if (change) { + out << format("Config file {1} modified.", Context::getContext().config.file()) << '\n'; + } else out << "No changes made.\n"; - } - else - throw std::string ("Specify the name of a config variable to modify."); + } else + throw std::string("Specify the name of a config variable to modify."); - output = out.str (); - } - else - throw std::string ("Specify the name of a config variable to modify."); + output = out.str(); + } else + throw std::string("Specify the name of a config variable to modify."); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionConfig::CmdCompletionConfig () -{ - _keyword = "_config"; - _usage = "task _config"; - _description = "Lists all supported configuration variables, for completion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionConfig::CmdCompletionConfig() { + _keyword = "_config"; + _usage = "task _config"; + _description = "Lists all supported configuration variables, for completion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionConfig::execute (std::string& output) -{ - auto configs = Context::getContext ().config.all (); - std::sort (configs.begin (), configs.end ()); +int CmdCompletionConfig::execute(std::string& output) { + auto configs = Context::getContext().config.all(); + std::sort(configs.begin(), configs.end()); - for (const auto& config : configs) - output += config + '\n'; + for (const auto& config : configs) output += config + '\n'; return 0; } diff --git a/src/commands/CmdConfig.h b/src/commands/CmdConfig.h index 4cf6c9583..866c83385 100644 --- a/src/commands/CmdConfig.h +++ b/src/commands/CmdConfig.h @@ -27,23 +27,22 @@ #ifndef INCLUDED_CMDCONFIG #define INCLUDED_CMDCONFIG -#include #include -class CmdConfig : public Command -{ -public: - CmdConfig (); - static bool setConfigVariable (const std::string&, const std::string&, bool confirmation = false); - static int unsetConfigVariable (const std::string&, bool confirmation = false); - int execute (std::string&); +#include + +class CmdConfig : public Command { + public: + CmdConfig(); + static bool setConfigVariable(const std::string&, const std::string&, bool confirmation = false); + static int unsetConfigVariable(const std::string&, bool confirmation = false); + int execute(std::string&); }; -class CmdCompletionConfig : public Command -{ -public: - CmdCompletionConfig (); - int execute (std::string&); +class CmdCompletionConfig : public Command { + public: + CmdCompletionConfig(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdContext.cpp b/src/commands/CmdContext.cpp index f8e6c3e2d..fae5d3818 100644 --- a/src/commands/CmdContext.cpp +++ b/src/commands/CmdContext.cpp @@ -25,60 +25,65 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include +// cmake.h include header must come first + #include -#include +#include #include #include -#include -#include -#include +#include #include -#include +#include #include #include +#include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdContext::CmdContext () -{ - _keyword = "context"; - _usage = "task context [ | ]"; - _description = "Set and define contexts (default filters / modifications)"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdContext::CmdContext() { + _keyword = "context"; + _usage = "task context [ | ]"; + _description = "Set and define contexts (default filters / modifications)"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::context; + _category = Command::Category::context; } //////////////////////////////////////////////////////////////////////////////// -int CmdContext::execute (std::string& output) -{ +int CmdContext::execute(std::string& output) { std::stringstream out; // Get the non-attribute, non-fancy command line arguments. - auto words = Context::getContext ().cli2.getWords (); - if (words.size () > 0) - { + auto words = Context::getContext().cli2.getWords(); + if (words.size() > 0) { auto subcommand = words[0]; - if (subcommand == "define") defineContext (words, out); - else if (subcommand == "delete") deleteContext (words, out); - else if (subcommand == "list") listContexts (out); - else if (subcommand == "none") unsetContext (out); - else if (subcommand == "show") showContext (out); - else if (words.size ()) setContext (words, out); - } - else - { - listContexts (out); + if (subcommand == "define") + defineContext(words, out); + else if (subcommand == "delete") + deleteContext(words, out); + else if (subcommand == "list") + listContexts(out); + else if (subcommand == "none") + unsetContext(out); + else if (subcommand == "show") + showContext(out); + else if (words.size()) + setContext(words, out); + } else { + listContexts(out); out << "Use 'task context none' to unset the current context.\n"; } - output = out.str (); + output = out.str(); return 0; } @@ -88,17 +93,14 @@ int CmdContext::execute (std::string& output) // // If to is specified as 0 (default value), all the remaining words will be joined. // -std::string CmdContext::joinWords (const std::vector & words, unsigned int from, unsigned int to /* = 0 */) -{ +std::string CmdContext::joinWords(const std::vector& words, unsigned int from, + unsigned int to /* = 0 */) { std::string value = ""; - if (to == 0) - to = words.size(); + if (to == 0) to = words.size(); - for (unsigned int i = from; i < to; ++i) - { - if (i > from) - value += ' '; + for (unsigned int i = from; i < to; ++i) { + if (i > from) value += ' '; value += words[i]; } @@ -117,33 +119,28 @@ std::string CmdContext::joinWords (const std::vector & words, unsig // invalid due to a wrong modifier use, the modifier string will contain the // first invalid modifier. // -bool CmdContext::validateWriteContext (const std::vector & lexedArgs, std::string& reason) -{ - for (auto &arg: lexedArgs) { +bool CmdContext::validateWriteContext(const std::vector& lexedArgs, std::string& reason) { + for (auto& arg : lexedArgs) { if (arg._lextype == Lexer::Type::op) - if (arg.attribute ("raw") == "or") - { + if (arg.attribute("raw") == "or") { reason = "contains the 'OR' operator"; return false; } if (arg._lextype == Lexer::Type::pair) { - auto modifier = arg.attribute ("modifier"); - if (modifier != "" && modifier != "is" && modifier != "equals") - { - reason = format ("contains an attribute modifier '{1}'", arg.attribute ("raw")); + auto modifier = arg.attribute("modifier"); + if (modifier != "" && modifier != "is" && modifier != "equals") { + reason = format("contains an attribute modifier '{1}'", arg.attribute("raw")); return false; } } if (arg._lextype == Lexer::Type::tag) { - if (arg.attribute ("sign") == "-") - { - reason = format ("contains tag exclusion '{1}'", arg.attribute ("raw")); + if (arg.attribute("sign") == "-") { + reason = format("contains tag exclusion '{1}'", arg.attribute("raw")); return false; } } - } return true; @@ -152,22 +149,20 @@ bool CmdContext::validateWriteContext (const std::vector & lexedArgs, std::s //////////////////////////////////////////////////////////////////////////////// // Returns all user defined contexts. // -std::vector CmdContext::getContexts () -{ - std::set contexts; +std::vector CmdContext::getContexts() { + std::set contexts; - for (auto& name : Context::getContext ().config) - if (name.first.substr (0, 8) == "context.") - { - std::string suffix = name.first.substr (8); + for (auto& name : Context::getContext().config) + if (name.first.substr(0, 8) == "context.") { + std::string suffix = name.first.substr(8); - if (suffix.find (".") != std::string::npos) - contexts.insert (suffix.substr (0, suffix.find ("."))); + if (suffix.find(".") != std::string::npos) + contexts.insert(suffix.substr(0, suffix.find("."))); else - contexts.insert (suffix); + contexts.insert(suffix); } - return std::vector (contexts.begin (), contexts.end ()); + return std::vector(contexts.begin(), contexts.end()); } //////////////////////////////////////////////////////////////////////////////// @@ -180,85 +175,85 @@ std::vector CmdContext::getContexts () // Invoked with: task context define // Example: task context define home project:Home // -void CmdContext::defineContext (const std::vector & words, std::stringstream& out) -{ - auto config = Context::getContext ().config; - bool confirmation = config.getBoolean ("confirmation"); +void CmdContext::defineContext(const std::vector& words, std::stringstream& out) { + auto config = Context::getContext().config; + bool confirmation = config.getBoolean("confirmation"); - if (words.size () > 2) - { + if (words.size() > 2) { auto name = "context." + words[1]; - auto value = joinWords (words, 2); + auto value = joinWords(words, 2); // Make sure nobody creates a context with name 'list', 'none' or 'show' - if (words[1] == "none" or words[1] == "list" or words[1] == "show") - { - throw format ("The name '{1}' is reserved and not allowed to use as a context name.", words[1]); + if (words[1] == "none" or words[1] == "list" or words[1] == "show") { + throw format("The name '{1}' is reserved and not allowed to use as a context name.", + words[1]); } // Extract MISCELLANEOUS arguments (containing the filter definition) for later analysis - std::vector lexedArgs = Context::getContext ().cli2.getMiscellaneous(); + std::vector lexedArgs = Context::getContext().cli2.getMiscellaneous(); // Check if the value is a proper filter by filtering current pending.data Filter filter; - std::vector filtered; - auto pending = Context::getContext ().tdb2.pending_tasks (); + std::vector filtered; + auto pending = Context::getContext().tdb2.pending_tasks(); - try - { + try { // This result is not used, and is just to check validity. - Context::getContext ().cli2.addFilter (value); - filter.subset (pending, filtered); - } - catch (std::string exception) - { - throw format ("Filter validation failed: {1}", exception); + Context::getContext().cli2.addFilter(value); + filter.subset(pending, filtered); + } catch (std::string exception) { + throw format("Filter validation failed: {1}", exception); } // Make user explicitly confirm filters that are matching no pending tasks - if (filtered.size () == 0) + if (filtered.size() == 0) if (confirmation && - ! confirm (format ("The filter '{1}' matches 0 pending tasks. Do you wish to continue?", value))) - throw std::string ("Context definition aborted."); + !confirm( + format("The filter '{1}' matches 0 pending tasks. Do you wish to continue?", value))) + throw std::string("Context definition aborted."); std::string reason = ""; - bool valid_write_context = CmdContext::validateWriteContext (lexedArgs, reason); + bool valid_write_context = CmdContext::validateWriteContext(lexedArgs, reason); - if (! valid_write_context) - { + if (!valid_write_context) { std::stringstream warning; - warning << format ("The filter '{1}' is not a valid modification string, because it contains {2}.", value, reason) - << "\nAs such, value for the write context cannot be set (context will not apply on task add / task log).\n\n" - << format ("Please use 'task config context.{1}.write ' to set default attribute values for new tasks in this context manually.\n\n", words[1]); - out << colorizeFootnote (warning.str ()); + warning + << format("The filter '{1}' is not a valid modification string, because it {2}.", value, + reason) + << "\nAs such, value for the write context cannot be set (context will not apply on task " + "add / task log).\n\n" + << format( + "Please use 'task config context.{1}.write ' to set default " + "attribute values for new tasks in this context manually.\n\n", + words[1]); + out << colorizeFootnote(warning.str()); } // Set context definition config variable - bool read_success = CmdConfig::setConfigVariable (name + ".read", value, confirmation); + bool read_success = CmdConfig::setConfigVariable(name + ".read", value, confirmation); bool write_success = false; if (valid_write_context) - write_success = CmdConfig::setConfigVariable (name + ".write", value, confirmation); + write_success = CmdConfig::setConfigVariable(name + ".write", value, confirmation); // Remove old-school context name, if it exists, assuming the read context was defined if (read_success) - if (config.has (name)) { - CmdConfig::unsetConfigVariable (name, false); + if (config.has(name)) { + CmdConfig::unsetConfigVariable(name, false); } if (!read_success and !write_success) - throw format ("Context '{1}' not defined.", words[1]); + throw format("Context '{1}' not defined.", words[1]); else if (!read_success) - out << format ("Context '{1}' defined (write only).", words[1]); + out << format("Context '{1}' defined (write only).", words[1]); else if (!write_success) - out << format ("Context '{1}' defined (read only).", words[1]); + out << format("Context '{1}' defined (read only).", words[1]); else - out << format ("Context '{1}' defined (read, write).", words[1]); + out << format("Context '{1}' defined (read, write).", words[1]); - out << format (" Use 'task context {1}' to activate.\n", words[1]); - } - else - throw std::string ("Both context name and its definition must be provided."); + out << format(" Use 'task context {1}' to activate.\n", words[1]); + } else + throw std::string("Both context name and its definition must be provided."); } //////////////////////////////////////////////////////////////////////////////// @@ -271,36 +266,33 @@ void CmdContext::defineContext (const std::vector & words, std::str // Invoked with: task context delete // Example: task context delete home // -void CmdContext::deleteContext (const std::vector & words, std::stringstream& out) -{ - if (words.size () > 1) - { +void CmdContext::deleteContext(const std::vector& words, std::stringstream& out) { + if (words.size() > 1) { // Delete the specified context auto name = "context." + words[1]; - auto confirmation = Context::getContext ().config.getBoolean ("confirmation"); - if (confirmation && ! confirm (format ("Do you want to delete context '{1}'?", words[1]))) - throw format ("Context '{1}' not deleted.", words[1]); + auto confirmation = Context::getContext().config.getBoolean("confirmation"); + if (confirmation && !confirm(format("Do you want to delete context '{1}'?", words[1]))) + throw format("Context '{1}' not deleted.", words[1]); // Delete legacy format and .read / .write flavours auto rc = CmdConfig::unsetConfigVariable(name, false); - rc += CmdConfig::unsetConfigVariable(name + ".read", false); - rc += CmdConfig::unsetConfigVariable(name + ".write", false); + rc += CmdConfig::unsetConfigVariable(name + ".read", false); + rc += CmdConfig::unsetConfigVariable(name + ".write", false); // If the currently set context was deleted, unset it - if (Context::getContext ().config.get ("context") == words[1]) + if (Context::getContext().config.get("context") == words[1]) CmdConfig::unsetConfigVariable("context", false); // Output feedback, rc should be even because only 0 (found and removed) // and 2 (not found) are aceptable return values from unsetConfigVariable if (rc % 2 != 0) - throw format ("Context '{1}' not deleted.", words[1]); + throw format("Context '{1}' not deleted.", words[1]); else if (rc == 6) - throw format ("Context '{1}' not found.", words[1]); + throw format("Context '{1}' not found.", words[1]); - out << format ("Context '{1}' deleted.\n", words[1]); - } - else + out << format("Context '{1}' deleted.\n", words[1]); + } else throw std::string("Context name needs to be specified."); } @@ -312,48 +304,41 @@ void CmdContext::deleteContext (const std::vector & words, std::str // Invoked with: task context list // Example: task context list // -void CmdContext::listContexts (std::stringstream& out) -{ +void CmdContext::listContexts(std::stringstream& out) { auto contexts = getContexts(); - if (contexts.size ()) - { - std::sort (contexts.begin (), contexts.end ()); + if (contexts.size()) { + std::sort(contexts.begin(), contexts.end()); Table table; - table.width (Context::getContext ().getWidth ()); - table.add ("Name"); - table.add ("Type"); - table.add ("Definition"); - table.add ("Active"); - setHeaderUnderline (table); + table.width(Context::getContext().getWidth()); + table.add("Name"); + table.add("Type"); + table.add("Definition"); + table.add("Active"); + setHeaderUnderline(table); - std::string activeContext = Context::getContext ().config.get ("context"); + std::string activeContext = Context::getContext().config.get("context"); - for (auto& userContext : contexts) - { + for (auto& userContext : contexts) { std::string active = "no"; - if (userContext == activeContext) - active = "yes"; + if (userContext == activeContext) active = "yes"; - int row = table.addRow (); - table.set (row, 0, userContext); - table.set (row, 1, "read"); - table.set (row, 2, Context::getContext ().getTaskContext("read", userContext)); - table.set (row, 3, active); + int row = table.addRow(); + table.set(row, 0, userContext); + table.set(row, 1, "read"); + table.set(row, 2, Context::getContext().getTaskContext("read", userContext)); + table.set(row, 3, active); - row = table.addRow (); - table.set (row, 0, ""); - table.set (row, 1, "write"); - table.set (row, 2, Context::getContext ().getTaskContext("write", userContext)); - table.set (row, 3, active); + row = table.addRow(); + table.set(row, 0, ""); + table.set(row, 1, "write"); + table.set(row, 2, Context::getContext().getTaskContext("write", userContext)); + table.set(row, 3, active); } - out << optionalBlankLine () - << table.render () - << optionalBlankLine (); - } - else - throw std::string ("No contexts defined."); + out << optionalBlankLine() << table.render() << optionalBlankLine(); + } else + throw std::string("No contexts defined."); } //////////////////////////////////////////////////////////////////////////////// @@ -367,23 +352,21 @@ void CmdContext::listContexts (std::stringstream& out) // Invoked with: task context // Example: task context home // -void CmdContext::setContext (const std::vector & words, std::stringstream& out) -{ +void CmdContext::setContext(const std::vector& words, std::stringstream& out) { auto value = words[0]; - auto contexts = getContexts (); + auto contexts = getContexts(); // Check that the specified context is defined - if (std::find (contexts.begin (), contexts.end (), value) == contexts.end ()) - throw format ("Context '{1}' not found.", value); + if (std::find(contexts.begin(), contexts.end(), value) == contexts.end()) + throw format("Context '{1}' not found.", value); // Set the active context. // Should always succeed, as we do not require confirmation. - bool success = CmdConfig::setConfigVariable ("context", value, false); + bool success = CmdConfig::setConfigVariable("context", value, false); - if (! success) - throw format ("Context '{1}' not applied.", value); + if (!success) throw format("Context '{1}' not applied.", value); - out << format ("Context '{1}' set. Use 'task context none' to remove.\n", value); + out << format("Context '{1}' set. Use 'task context none' to remove.\n", value); } //////////////////////////////////////////////////////////////////////////////// @@ -394,20 +377,17 @@ void CmdContext::setContext (const std::vector & words, std::string // Invoked with: task context show // Example: task context show // -void CmdContext::showContext (std::stringstream& out) -{ - auto currentContext = Context::getContext ().config.get ("context"); +void CmdContext::showContext(std::stringstream& out) { + auto currentContext = Context::getContext().config.get("context"); if (currentContext == "") out << "No context is currently applied.\n"; - else - { - out << format ( - "Context '{1}' with \n\n* read filter: '{2}'\n* write filter: '{3}'\n\nis currently applied.\n", - currentContext, - Context::getContext ().getTaskContext("read", ""), - Context::getContext ().getTaskContext("write", "") - ); + else { + out << format( + "Context '{1}' with \n\n* read filter: '{2}'\n* write filter: '{3}'\n\nis currently " + "applied.\n", + currentContext, Context::getContext().getTaskContext("read", ""), + Context::getContext().getTaskContext("write", "")); } } @@ -421,35 +401,31 @@ void CmdContext::showContext (std::stringstream& out) // Invoked with: task context none // Example: task context none // -void CmdContext::unsetContext (std::stringstream& out) -{ - if (CmdConfig::unsetConfigVariable ("context", false)) - throw std::string ("Context not unset."); +void CmdContext::unsetContext(std::stringstream& out) { + if (CmdConfig::unsetConfigVariable("context", false)) throw std::string("Context not unset."); out << "Context unset.\n"; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionContext::CmdCompletionContext () -{ - _keyword = "_context"; - _usage = "task _context"; - _description = "Lists all supported contexts, for completion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionContext::CmdCompletionContext() { + _keyword = "_context"; + _usage = "task _context"; + _description = "Lists all supported contexts, for completion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionContext::execute (std::string& output) -{ - for (auto& context : CmdContext::getContexts ()) - output += context + '\n'; +int CmdCompletionContext::execute(std::string& output) { + for (auto& context : CmdContext::getContexts()) output += context + '\n'; return 0; } diff --git a/src/commands/CmdContext.h b/src/commands/CmdContext.h index c21e8a0b3..a14197ccb 100644 --- a/src/commands/CmdContext.h +++ b/src/commands/CmdContext.h @@ -27,31 +27,30 @@ #ifndef INCLUDED_CMDCONTEXT #define INCLUDED_CMDCONTEXT -#include -#include #include +#include -class CmdContext : public Command -{ -public: - CmdContext (); - int execute (std::string&); - std::string joinWords (const std::vector &, unsigned int, unsigned int = 0); - bool validateWriteContext (const std::vector &, std::string&); - static std::vector getContexts (); - void defineContext (const std::vector &, std::stringstream&); - void deleteContext (const std::vector &, std::stringstream&); - void listContexts (std::stringstream&); - void setContext (const std::vector &, std::stringstream&); - void showContext (std::stringstream&); - void unsetContext (std::stringstream&); +#include + +class CmdContext : public Command { + public: + CmdContext(); + int execute(std::string&); + std::string joinWords(const std::vector&, unsigned int, unsigned int = 0); + bool validateWriteContext(const std::vector&, std::string&); + static std::vector getContexts(); + void defineContext(const std::vector&, std::stringstream&); + void deleteContext(const std::vector&, std::stringstream&); + void listContexts(std::stringstream&); + void setContext(const std::vector&, std::stringstream&); + void showContext(std::stringstream&); + void unsetContext(std::stringstream&); }; -class CmdCompletionContext : public Command -{ -public: - CmdCompletionContext (); - int execute (std::string&); +class CmdCompletionContext : public Command { + public: + CmdCompletionContext(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdCount.cpp b/src/commands/CmdCount.cpp index 14c6f442b..e25af8fde 100644 --- a/src/commands/CmdCount.cpp +++ b/src/commands/CmdCount.cpp @@ -25,40 +25,39 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include -#include #include //////////////////////////////////////////////////////////////////////////////// -CmdCount::CmdCount () -{ - _keyword = "count"; - _usage = "task count"; - _description = "Counts matching tasks"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdCount::CmdCount() { + _keyword = "count"; + _usage = "task count"; + _description = "Counts matching tasks"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdCount::execute (std::string& output) -{ +int CmdCount::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Find number of matching tasks. Skip recurring parent tasks. - int count = std::count_if(filtered.begin(), filtered.end(), [](const auto& task){ return task.getStatus () != Task::recurring; }); - output = format (count) + '\n'; + int count = std::count_if(filtered.begin(), filtered.end(), + [](const auto& task) { return task.getStatus() != Task::recurring; }); + output = format(count) + '\n'; return 0; } diff --git a/src/commands/CmdCount.h b/src/commands/CmdCount.h index bea9f6851..a7d49cfb8 100644 --- a/src/commands/CmdCount.h +++ b/src/commands/CmdCount.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDCOUNT #define INCLUDED_CMDCOUNT -#include #include -class CmdCount : public Command -{ -public: - CmdCount (); - int execute (std::string&); +#include + +class CmdCount : public Command { + public: + CmdCount(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdCustom.cpp b/src/commands/CmdCustom.cpp index d6e1b7503..8bba6c902 100644 --- a/src/commands/CmdCustom.cpp +++ b/src/commands/CmdCustom.cpp @@ -25,179 +25,158 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include -#include +#include #include #include #include #include #include +#include #include +#include #include +#include #include -#include + +#include +#include +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdCustom::CmdCustom ( - const std::string& keyword, - const std::string& usage, - const std::string& description) -{ - _keyword = keyword; - _usage = usage; - _description = description; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdCustom::CmdCustom(const std::string& keyword, const std::string& usage, + const std::string& description) { + _keyword = keyword; + _usage = usage; + _description = description; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Category::report; + _category = Category::report; } //////////////////////////////////////////////////////////////////////////////// // Whether a report uses context is defined by the report..context // configuration variable. // -bool CmdCustom::uses_context () const -{ - auto config = Context::getContext ().config; +bool CmdCustom::uses_context() const { + auto config = Context::getContext().config; auto key = "report." + _keyword + ".context"; - if (config.has (key)) - return config.getBoolean (key); + if (config.has(key)) + return config.getBoolean(key); else return _uses_context; } //////////////////////////////////////////////////////////////////////////////// -int CmdCustom::execute (std::string& output) -{ +int CmdCustom::execute(std::string& output) { auto rc = 0; // Load report configuration. - auto reportColumns = Context::getContext ().config.get ("report." + _keyword + ".columns"); - auto reportLabels = Context::getContext ().config.get ("report." + _keyword + ".labels"); - auto reportSort = Context::getContext ().config.get ("report." + _keyword + ".sort"); - auto reportFilter = Context::getContext ().config.get ("report." + _keyword + ".filter"); + auto reportColumns = Context::getContext().config.get("report." + _keyword + ".columns"); + auto reportLabels = Context::getContext().config.get("report." + _keyword + ".labels"); + auto reportSort = Context::getContext().config.get("report." + _keyword + ".sort"); + auto reportFilter = Context::getContext().config.get("report." + _keyword + ".filter"); - auto columns = split (reportColumns, ','); - validateReportColumns (columns); + auto columns = split(reportColumns, ','); + validateReportColumns(columns); - auto labels = split (reportLabels, ','); + auto labels = split(reportLabels, ','); - if (columns.size () != labels.size () && labels.size () != 0) - throw format ("There are different numbers of columns and labels for report '{1}'.", _keyword); + if (columns.size() != labels.size() && labels.size() != 0) + throw format("There are different numbers of columns and labels for report '{1}'.", _keyword); - auto sortOrder = split (reportSort, ','); - if (sortOrder.size () != 0 && - sortOrder[0] != "none") - validateSortColumns (sortOrder); + auto sortOrder = split(reportSort, ','); + if (sortOrder.size() != 0 && sortOrder[0] != "none") validateSortColumns(sortOrder); // Add the report filter to any existing filter. - if (reportFilter != "") - Context::getContext ().cli2.addFilter (reportFilter); - - // Make sure reccurent tasks are generated. - handleUntil (); - handleRecurrence (); + if (reportFilter != "") Context::getContext().cli2.addFilter(reportFilter); // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - std::vector sequence; - if (sortOrder.size () && - sortOrder[0] == "none") - { + std::vector sequence; + if (sortOrder.size() && sortOrder[0] == "none") { // Assemble a sequence vector that represents the tasks listed in // Context::getContext ().cli2._uuid_ranges, in the order in which they appear. This // equates to no sorting, just a specified order. - sortOrder.clear (); - for (auto& i : Context::getContext ().cli2._uuid_list) - for (unsigned int t = 0; t < filtered.size (); ++t) - if (filtered[t].get ("uuid") == i) - sequence.push_back (t); - } - else - { + sortOrder.clear(); + for (auto& i : Context::getContext().cli2._uuid_list) + for (unsigned int t = 0; t < filtered.size(); ++t) + if (filtered[t].get("uuid") == i) sequence.push_back(t); + } else { // There is a sortOrder, so sorting will take place, which means the initial // order of sequence is ascending. - for (unsigned int i = 0; i < filtered.size (); ++i) - sequence.push_back (i); + for (unsigned int i = 0; i < filtered.size(); ++i) sequence.push_back(i); // Sort the tasks. - if (sortOrder.size ()) - sort_tasks (filtered, sequence, reportSort); + if (sortOrder.size()) sort_tasks(filtered, sequence, reportSort); } // Configure the view. ViewTask view; - view.width (Context::getContext ().getWidth ()); - view.leftMargin (Context::getContext ().config.getInteger ("indent.report")); - view.extraPadding (Context::getContext ().config.getInteger ("row.padding")); - view.intraPadding (Context::getContext ().config.getInteger ("column.padding")); + view.width(Context::getContext().getWidth()); + view.leftMargin(Context::getContext().config.getInteger("indent.report")); + view.extraPadding(Context::getContext().config.getInteger("row.padding")); + view.intraPadding(Context::getContext().config.getInteger("column.padding")); - if (Context::getContext ().color ()) - { - Color label (Context::getContext ().config.get ("color.label")); - view.colorHeader (label); + if (Context::getContext().color()) { + Color label(Context::getContext().config.get("color.label")); + view.colorHeader(label); - Color label_sort (Context::getContext ().config.get ("color.label.sort")); - view.colorSortHeader (label_sort); + Color label_sort(Context::getContext().config.get("color.label.sort")); + view.colorSortHeader(label_sort); // If an alternating row color is specified, notify the table. - Color alternate (Context::getContext ().config.get ("color.alternate")); - if (alternate.nontrivial ()) - { - view.colorOdd (alternate); - view.intraColorOdd (alternate); + Color alternate(Context::getContext().config.get("color.alternate")); + if (alternate.nontrivial()) { + view.colorOdd(alternate); + view.intraColorOdd(alternate); } } // Capture columns that are sorted. - std::vector sortColumns; + std::vector sortColumns; // Add the break columns, if any. - for (const auto& so : sortOrder) - { + for (const auto& so : sortOrder) { std::string name; bool ascending; bool breakIndicator; - Context::getContext ().decomposeSortField (so, name, ascending, breakIndicator); + Context::getContext().decomposeSortField(so, name, ascending, breakIndicator); - if (breakIndicator) - view.addBreak (name); + if (breakIndicator) view.addBreak(name); - sortColumns.push_back (name); + sortColumns.push_back(name); } // Add the columns and labels. - for (unsigned int i = 0; i < columns.size (); ++i) - { - Column* c = Column::factory (columns[i], _keyword); - if (i < labels.size ()) - c->setLabel (labels[i]); + for (unsigned int i = 0; i < columns.size(); ++i) { + Column* c = Column::factory(columns[i], _keyword); + if (i < labels.size()) c->setLabel(labels[i]); - bool sort = std::find (sortColumns.begin (), sortColumns.end (), c->name ()) != sortColumns.end () - ? true - : false; + bool sort = std::find(sortColumns.begin(), sortColumns.end(), c->name()) != sortColumns.end() + ? true + : false; - view.add (c, sort); + view.add(c, sort); } // How many lines taken up by table header? int table_header = 0; - if (Context::getContext ().verbose ("label")) - { - if (Context::getContext ().color () && Context::getContext ().config.getBoolean ("fontunderline")) + if (Context::getContext().verbose("label")) { + if (Context::getContext().color() && Context::getContext().config.getBoolean("fontunderline")) table_header = 1; // Underlining doesn't use extra line. else table_header = 2; // Dashes use an extra line. @@ -206,90 +185,76 @@ int CmdCustom::execute (std::string& output) // Report output can be limited by rows or lines. auto maxrows = 0; auto maxlines = 0; - Context::getContext ().getLimits (maxrows, maxlines); + Context::getContext().getLimits(maxrows, maxlines); // Adjust for fluff in the output. if (maxlines) - maxlines -= table_header - + (Context::getContext ().verbose ("blank") ? 1 : 0) - + (Context::getContext ().verbose ("footnote") ? Context::getContext ().footnotes.size () : 0) - + (Context::getContext ().verbose ("affected") ? 1 : 0) - + Context::getContext ().config.getInteger ("reserved.lines"); // For prompt, etc. + maxlines -= + table_header + (Context::getContext().verbose("blank") ? 1 : 0) + + (Context::getContext().verbose("footnote") ? Context::getContext().footnotes.size() : 0) + + (Context::getContext().verbose("affected") ? 1 : 0) + + Context::getContext().config.getInteger("reserved.lines"); // For prompt, etc. // Render. std::stringstream out; - if (filtered.size ()) - { - view.truncateRows (maxrows); - view.truncateLines (maxlines); + if (filtered.size()) { + view.truncateRows(maxrows); + view.truncateLines(maxlines); - out << optionalBlankLine () - << view.render (filtered, sequence) - << optionalBlankLine (); + out << optionalBlankLine() << view.render(filtered, sequence) << optionalBlankLine(); // Print the number of rendered tasks - if (Context::getContext ().verbose ("affected")) - { - out << (filtered.size () == 1 - ? "1 task" - : format ("{1} tasks", filtered.size ())); + if (Context::getContext().verbose("affected")) { + out << (filtered.size() == 1 ? "1 task" : format("{1} tasks", filtered.size())); - if (maxrows && maxrows < (int)filtered.size ()) - out << ", " << format ("{1} shown", maxrows); + if (maxrows && maxrows < (int)filtered.size()) out << ", " << format("{1} shown", maxrows); - if (maxlines && maxlines < (int)filtered.size ()) - out << ", " - << format ("truncated to {1} lines", maxlines - table_header); + if (maxlines && maxlines < (int)filtered.size()) + out << ", " << format("truncated to {1} lines", maxlines - table_header); out << '\n'; } - } - else - { - Context::getContext ().footnote ("No matches."); + } else { + Context::getContext().footnote("No matches."); rc = 1; } // Inform user about the new release highlights if not presented yet - Version news_version(Context::getContext ().config.get ("news.version")); - Version current_version = Version::Current(); - if (news_version != current_version) - { - std::random_device device; - std::mt19937 random_generator(device()); - std::uniform_int_distribution twentyfive_percent(1, 4); - - // 1 in 10 chance to display the message. - if (twentyfive_percent(random_generator) == 4) - { - std::ostringstream notice; - notice << "Recently upgraded to " << current_version << ". " - "Please run 'task news' to read highlights about the new release."; - if (Context::getContext ().verbose ("footnote")) - Context::getContext ().footnote (notice.str()); - else if (Context::getContext ().verbose ("header")) - Context::getContext ().header (notice.str()); - } + if (CmdNews::should_nag()) { + std::ostringstream notice; + Version current_version = Version::Current(); + notice << "Recently upgraded to " << current_version + << ". " + "Please run 'task news' to read highlights about the new release."; + if (Context::getContext().verbose("footnote")) + Context::getContext().footnote(notice.str()); + else if (Context::getContext().verbose("header")) + Context::getContext().header(notice.str()); } + std::string location = (Context::getContext().data_dir); + File pending_data = File(location + "/pending.data"); + if (pending_data.exists()) { + Color warning = Color(Context::getContext().config.get("color.warning")); + std::cerr << warning.colorize(format("Found existing '*.data' files in {1}", location)) << "\n"; + std::cerr << " Taskwarrior's storage format changed in 3.0, requiring a manual migration.\n"; + std::cerr << " See https://taskwarrior.org/docs/upgrade-3/. Run `task import-v2` to import\n"; + std::cerr << " the tasks into the Taskwarrior-3.x format\n"; + } - feedback_backlog (); - output = out.str (); + feedback_backlog(); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -void CmdCustom::validateReportColumns (std::vector & columns) -{ - for (auto& col : columns) - legacyColumnMap (col); +void CmdCustom::validateReportColumns(std::vector& columns) { + for (auto& col : columns) legacyColumnMap(col); } //////////////////////////////////////////////////////////////////////////////// -void CmdCustom::validateSortColumns (std::vector & columns) -{ - for (auto& col : columns) - legacySortColumnMap (col); +void CmdCustom::validateSortColumns(std::vector& columns) { + for (auto& col : columns) legacySortColumnMap(col); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdCustom.h b/src/commands/CmdCustom.h index 1c805a45c..eeefe6302 100644 --- a/src/commands/CmdCustom.h +++ b/src/commands/CmdCustom.h @@ -27,19 +27,19 @@ #ifndef INCLUDED_CMDCUSTOM #define INCLUDED_CMDCUSTOM -#include #include -class CmdCustom : public Command -{ -public: - CmdCustom (const std::string&, const std::string&, const std::string&); - bool uses_context () const override; - int execute (std::string&); +#include -private: - void validateReportColumns (std::vector &); - void validateSortColumns (std::vector &); +class CmdCustom : public Command { + public: + CmdCustom(const std::string&, const std::string&, const std::string&); + bool uses_context() const override; + int execute(std::string&) override; + + private: + void validateReportColumns(std::vector&); + void validateSortColumns(std::vector&); }; #endif diff --git a/src/commands/CmdDelete.cpp b/src/commands/CmdDelete.cpp index 8bedab285..35b5c7ecf 100644 --- a/src/commands/CmdDelete.cpp +++ b/src/commands/CmdDelete.cpp @@ -25,166 +25,148 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include +#include #include -#include +#include +#include -#define STRING_CMD_DELETE_TASK_R "Deleting recurring task {1} '{2}'." -#define STRING_CMD_DELETE_CONFIRM_R "This is a recurring task. Do you want to delete all pending recurrences of this same task?" +#include + +#define STRING_CMD_DELETE_TASK_R "Deleting recurring task {1} '{2}'." +#define STRING_CMD_DELETE_CONFIRM_R \ + "This is a recurring task. Do you want to delete all pending recurrences of this same task?" //////////////////////////////////////////////////////////////////////////////// -CmdDelete::CmdDelete () -{ - _keyword = "delete"; - _usage = "task delete "; - _description = "Deletes the specified task"; - _read_only = false; - _displays_id = false; - _needs_confirm = true; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdDelete::CmdDelete() { + _keyword = "delete"; + _usage = "task delete "; + _description = "Deletes the specified task"; + _read_only = false; + _displays_id = false; + _needs_confirm = true; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdDelete::execute (std::string&) -{ +int CmdDelete::execute(std::string&) { auto rc = 0; auto count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); + for (auto& task : filtered) { + Task before(task); - if (task.getStatus () != Task::deleted) - { + if (task.getStatus() != Task::deleted) { // Delete the specified task. std::string question; - question = format ("Delete task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + question = format("Delete task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modAnnotate); - task.setStatus (Task::deleted); - if (! task.has ("end")) - task.setAsNow ("end"); + task.modify(Task::modAnnotate); + task.setStatus(Task::deleted); + if (!task.has("end")) task.setAsNow("end"); - if (permission (question, filtered.size ())) - { - updateRecurrenceMask (task); + if (permission(question, filtered.size())) { + updateRecurrenceMask(task); ++count; - Context::getContext ().tdb2.modify (task); - feedback_affected ("Deleting task {1} '{2}'.", task); - feedback_unblocked (task); - dependencyChainOnComplete (task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task); + Context::getContext().tdb2.modify(task); + feedback_affected("Deleting task {1} '{2}'.", task); + feedback_unblocked(task); + dependencyChainOnComplete(task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task); // Delete siblings. - if (task.has ("parent")) - { - if ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm (STRING_CMD_DELETE_CONFIRM_R)) || - Context::getContext ().config.getBoolean ("recurrence.confirmation")) - { - std::vector siblings = Context::getContext ().tdb2.siblings (task); - for (auto& sibling : siblings) - { - sibling.modify (Task::modAnnotate); - sibling.setStatus (Task::deleted); - if (! sibling.has ("end")) - sibling.setAsNow ("end"); + if (task.has("parent")) { + if ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm(STRING_CMD_DELETE_CONFIRM_R)) || + Context::getContext().config.getBoolean("recurrence.confirmation")) { + std::vector siblings = Context::getContext().tdb2.siblings(task); + for (auto& sibling : siblings) { + sibling.modify(Task::modAnnotate); + sibling.setStatus(Task::deleted); + if (!sibling.has("end")) sibling.setAsNow("end"); - updateRecurrenceMask (sibling); - Context::getContext ().tdb2.modify (sibling); - feedback_affected (STRING_CMD_DELETE_TASK_R, sibling); - feedback_unblocked (sibling); + updateRecurrenceMask(sibling); + Context::getContext().tdb2.modify(sibling); + feedback_affected(STRING_CMD_DELETE_TASK_R, sibling); + feedback_unblocked(sibling); ++count; } // Delete the parent Task parent; - Context::getContext ().tdb2.get (task.get ("parent"), parent); - parent.setStatus (Task::deleted); - if (! parent.has ("end")) - parent.setAsNow ("end"); + Context::getContext().tdb2.get(task.get("parent"), parent); + parent.setStatus(Task::deleted); + if (!parent.has("end")) parent.setAsNow("end"); - Context::getContext ().tdb2.modify (parent); + Context::getContext().tdb2.modify(parent); } } // Task potentially has child tasks - optionally delete them. - else - { - std::vector children = Context::getContext ().tdb2.children (task); + else { + std::vector children = Context::getContext().tdb2.children(task); if (children.size() && - ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm (STRING_CMD_DELETE_CONFIRM_R)) || - Context::getContext ().config.getBoolean ("recurrence.confirmation"))) - { - for (auto& child : children) - { - child.modify (Task::modAnnotate); - child.setStatus (Task::deleted); - if (! child.has ("end")) - child.setAsNow ("end"); + ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm(STRING_CMD_DELETE_CONFIRM_R)) || + Context::getContext().config.getBoolean("recurrence.confirmation"))) { + for (auto& child : children) { + child.modify(Task::modAnnotate); + child.setStatus(Task::deleted); + if (!child.has("end")) child.setAsNow("end"); - updateRecurrenceMask (child); - Context::getContext ().tdb2.modify (child); - feedback_affected (STRING_CMD_DELETE_TASK_R, child); - feedback_unblocked (child); + updateRecurrenceMask(child); + Context::getContext().tdb2.modify(child); + feedback_affected(STRING_CMD_DELETE_TASK_R, child); + feedback_unblocked(child); ++count; } } } - } - else - { + } else { std::cout << "Task not deleted.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } - } - else - { - std::cout << format ("Task {1} '{2}' is not deletable.", - task.identifier (true), - task.get ("description")) - << '\n'; + } else { + std::cout << format("Task {1} '{2}' is not deletable.", task.identifier(true), + task.get("description")) + << '\n'; rc = 1; } } // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Deleted {1} task." : "Deleted {1} tasks.", count); + feedback_affected(count == 1 ? "Deleted {1} task." : "Deleted {1} tasks.", count); return rc; } diff --git a/src/commands/CmdDelete.h b/src/commands/CmdDelete.h index 94345e622..2cc604b02 100644 --- a/src/commands/CmdDelete.h +++ b/src/commands/CmdDelete.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDDELETE #define INCLUDED_CMDDELETE -#include #include -class CmdDelete : public Command -{ -public: - CmdDelete (); - int execute (std::string&); +#include + +class CmdDelete : public Command { + public: + CmdDelete(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdDenotate.cpp b/src/commands/CmdDenotate.cpp index ac73f940b..7293f8ace 100644 --- a/src/commands/CmdDenotate.cpp +++ b/src/commands/CmdDenotate.cpp @@ -25,144 +25,128 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include #include +#include #include -#include -#define STRING_CMD_DENO_NO "Task not denotated." -#define STRING_CMD_DENO_1 "Denotated {1} task." -#define STRING_CMD_DENO_N "Denotated {1} tasks." +#include + +#define STRING_CMD_DENO_NO "Task not denotated." +#define STRING_CMD_DENO_1 "Denotated {1} task." +#define STRING_CMD_DENO_N "Denotated {1} tasks." //////////////////////////////////////////////////////////////////////////////// -CmdDenotate::CmdDenotate () -{ - _keyword = "denotate"; - _usage = "task denotate "; - _description = "Deletes an annotation"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdDenotate::CmdDenotate() { + _keyword = "denotate"; + _usage = "task denotate "; + _description = "Deletes an annotation"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdDenotate::execute (std::string&) -{ +int CmdDenotate::execute(std::string&) { auto rc = 0; auto count = 0; - auto sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive"); + auto sensitive = Context::getContext().config.getBoolean("search.case.sensitive"); // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Extract all the ORIGINAL MODIFICATION args as simple text patterns. std::string pattern = ""; - for (auto& a : Context::getContext ().cli2._args) - { - if (a.hasTag ("MISCELLANEOUS")) - { - if (pattern != "") - pattern += ' '; + for (auto& a : Context::getContext().cli2._args) { + if (a.hasTag("MISCELLANEOUS")) { + if (pattern != "") pattern += ' '; - pattern += a.attribute ("raw"); + pattern += a.attribute("raw"); } } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); + for (auto& task : filtered) { + Task before(task); - auto annotations = task.getAnnotations (); + auto annotations = task.getAnnotations(); - if (annotations.size () == 0) - throw std::string ("The specified task has no annotations that can be deleted."); + if (annotations.size() == 0) + throw std::string("The specified task has no annotations that can be deleted."); std::string anno; auto match = false; - for (auto i = annotations.begin (); i != annotations.end (); ++i) - { - if (i->second == pattern) - { + for (auto i = annotations.begin(); i != annotations.end(); ++i) { + if (i->second == pattern) { match = true; anno = i->second; - annotations.erase (i); - task.setAnnotations (annotations); + annotations.erase(i); + task.setAnnotations(annotations); break; } } - if (! match) - { - for (auto i = annotations.begin (); i != annotations.end (); ++i) - { - auto loc = find (i->second, pattern, sensitive); - if (loc != std::string::npos) - { + if (!match) { + for (auto i = annotations.begin(); i != annotations.end(); ++i) { + auto loc = find(i->second, pattern, sensitive); + if (loc != std::string::npos) { anno = i->second; - annotations.erase (i); - task.setAnnotations (annotations); + annotations.erase(i); + task.setAnnotations(annotations); break; } } } - if (before.getAnnotations () != task.getAnnotations ()) - { - auto question = format ("Denotate task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + if (before.getAnnotations() != task.getAnnotations()) { + auto question = + format("Denotate task {1} '{2}'?", task.identifier(true), task.get("description")); - if (permission (before.diff (task) + question, filtered.size ())) - { + if (permission(before.diff(task) + question, filtered.size())) { ++count; - Context::getContext ().tdb2.modify (task); - feedback_affected (format ("Found annotation '{1}' and deleted it.", anno)); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); - } - else - { + Context::getContext().tdb2.modify(task); + feedback_affected(format("Found annotation '{1}' and deleted it.", anno)); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); + } else { std::cout << STRING_CMD_DENO_NO << '\n'; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } - } - else - { - std::cout << format ("Did not find any matching annotation to be deleted for '{1}'.\n", pattern); + } else { + std::cout << format("Did not find any matching annotation to be deleted for '{1}'.\n", + pattern); rc = 1; } } // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? STRING_CMD_DENO_1 : STRING_CMD_DENO_N, count); + feedback_affected(count == 1 ? STRING_CMD_DENO_1 : STRING_CMD_DENO_N, count); return rc; } diff --git a/src/commands/CmdDenotate.h b/src/commands/CmdDenotate.h index 87ffd0f17..e45c9e2b1 100644 --- a/src/commands/CmdDenotate.h +++ b/src/commands/CmdDenotate.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDDENOTATE #define INCLUDED_CMDDENOTATE -#include #include -class CmdDenotate : public Command -{ -public: - CmdDenotate (); - int execute (std::string&); +#include + +class CmdDenotate : public Command { + public: + CmdDenotate(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdDiagnostics.cpp b/src/commands/CmdDiagnostics.cpp index 6d274bfc3..8baa39537 100644 --- a/src/commands/CmdDiagnostics.cpp +++ b/src/commands/CmdDiagnostics.cpp @@ -25,34 +25,37 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include + +#include +#include +#include #ifdef HAVE_COMMIT #include #endif //////////////////////////////////////////////////////////////////////////////// -CmdDiagnostics::CmdDiagnostics () -{ - _keyword = "diagnostics"; - _usage = "task diagnostics"; - _description = "Platform, build and environment details"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdDiagnostics::CmdDiagnostics() { + _keyword = "diagnostics"; + _usage = "task diagnostics"; + _description = "Platform, build and environment details"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// @@ -60,26 +63,19 @@ CmdDiagnostics::CmdDiagnostics () // // Although this will change over time, initially this command will answer the // kind of questions we always have to ask whenever something is wrong. -int CmdDiagnostics::execute (std::string& output) -{ +int CmdDiagnostics::execute(std::string& output) { Color bold; - if (Context::getContext ().color ()) - bold = Color ("bold"); + if (Context::getContext().color()) bold = Color("bold"); std::stringstream out; - out << '\n' - << bold.colorize (PACKAGE_STRING) - << '\n'; + out << '\n' << bold.colorize(PACKAGE_STRING) << '\n'; - out << " Platform: " << osName () - << "\n\n"; + out << " Platform: " << osName() << "\n\n"; // Compiler. - out << bold.colorize ("Compiler") - << '\n' + out << bold.colorize("Compiler") << '\n' #ifdef __VERSION__ - << " Version: " - << __VERSION__ << '\n' + << " Version: " << __VERSION__ << '\n' #endif << " Caps:" #ifdef __STDC__ @@ -103,20 +99,13 @@ int CmdDiagnostics::execute (std::string& output) #ifdef _LP64 << " +LP64" #endif - << " +c" << 8 * sizeof (char) - << " +i" << 8 * sizeof (int) - << " +l" << 8 * sizeof (long) - << " +vp" << 8 * sizeof (void*) - << " +time_t" << 8 * sizeof (time_t) - << '\n'; + << " +c" << 8 * sizeof(char) << " +i" << 8 * sizeof(int) << " +l" << 8 * sizeof(long) + << " +vp" << 8 * sizeof(void*) << " +time_t" << 8 * sizeof(time_t) << '\n'; // Compiler compliance level. - out << " Compliance: " - << cppCompliance () - << "\n\n"; + out << " Compliance: " << cppCompliance() << "\n\n"; - out << bold.colorize ("Build Features") - << '\n' + out << bold.colorize("Build Features") << '\n' #ifdef HAVE_COMMIT << " Commit: " << COMMIT << '\n' @@ -140,238 +129,146 @@ int CmdDiagnostics::execute (std::string& output) << "\n\n"; // Config: .taskrc found, readable, writable - File rcFile (Context::getContext ().config.file ()); - out << bold.colorize ("Configuration") - << '\n' - << " File: " << rcFile._data << ' ' - << (rcFile.exists () - ? "(found)" - : "(missing)") - << ", " << rcFile.size () << ' ' << "bytes" - << ", mode " - << std::setbase (8) - << rcFile.mode () - << '\n'; + File rcFile(Context::getContext().config.file()); + out << bold.colorize("Configuration") << '\n' + << " File: " << rcFile._data << ' ' << (rcFile.exists() ? "(found)" : "(missing)") + << ", " << rcFile.size() << ' ' << "bytes" + << ", mode " << std::setbase(8) << rcFile.mode() << '\n'; // Config: data.location found, readable, writable - File location (Context::getContext ().config.get ("data.location")); - out << " Data: " << location._data << ' ' - << (location.exists () - ? "(found)" - : "(missing)") - << ", " << (location.is_directory () ? "dir" : "?") - << ", mode " - << std::setbase (8) - << location.mode () - << '\n'; + File location(Context::getContext().config.get("data.location")); + out << " Data: " << location._data << ' ' << (location.exists() ? "(found)" : "(missing)") + << ", " << (location.is_directory() ? "dir" : "?") << ", mode " << std::setbase(8) + << location.mode() << '\n'; - char* env = getenv ("TASKRC"); - if (env) - out << " TASKRC: " - << env - << '\n'; + char* env = getenv("TASKRC"); + if (env) out << " TASKRC: " << env << '\n'; - env = getenv ("TASKDATA"); - if (env) - out << " TASKDATA: " - << env - << '\n'; + env = getenv("TASKDATA"); + if (env) out << " TASKDATA: " << env << '\n'; - out << " Locking: " - << (Context::getContext ().config.getBoolean ("locking") - ? "Enabled" - : "Disabled") - << '\n'; - - out << " GC: " - << (Context::getContext ().config.getBoolean ("gc") - ? "Enabled" - : "Disabled") + out << " GC: " << (Context::getContext().config.getBoolean("gc") ? "Enabled" : "Disabled") << '\n'; // Determine rc.editor/$EDITOR/$VISUAL. char* peditor; - if (Context::getContext ().config.get ("editor") != "") - out << " rc.editor: " << Context::getContext ().config.get ("editor") << '\n'; - else if ((peditor = getenv ("VISUAL")) != nullptr) + if (Context::getContext().config.get("editor") != "") + out << " rc.editor: " << Context::getContext().config.get("editor") << '\n'; + else if ((peditor = getenv("VISUAL")) != nullptr) out << " $VISUAL: " << peditor << '\n'; - else if ((peditor = getenv ("EDITOR")) != nullptr) + else if ((peditor = getenv("EDITOR")) != nullptr) out << " $EDITOR: " << peditor << '\n'; // Display hook status. Path hookLocation; - if (Context::getContext ().config.has ("hooks.location")) - { - hookLocation = Path (Context::getContext ().config.get ("hooks.location")); - } - else - { - hookLocation = Path (Context::getContext ().config.get ("data.location")); + if (Context::getContext().config.has("hooks.location")) { + hookLocation = Path(Context::getContext().config.get("hooks.location")); + } else { + hookLocation = Path(Context::getContext().config.get("data.location")); hookLocation += "hooks"; } - out << bold.colorize ("Hooks") - << '\n' + out << bold.colorize("Hooks") << '\n' << " System: " - << (Context::getContext ().config.getBoolean ("hooks") ? "Enabled" : "Disabled") - << '\n' - << " Location: " - << static_cast (hookLocation) - << '\n'; + << (Context::getContext().config.getBoolean("hooks") ? "Enabled" : "Disabled") << '\n' + << " Location: " << static_cast(hookLocation) << '\n'; - auto hooks = Context::getContext ().hooks.list (); - if (hooks.size ()) - { + auto hooks = Context::getContext().hooks.list(); + if (hooks.size()) { unsigned int longest = 0; for (auto& hook : hooks) - if (hook.length () > longest) - longest = hook.length (); - longest -= hookLocation._data.length () + 1; + if (hook.length() > longest) longest = hook.length(); + longest -= hookLocation._data.length() + 1; out << " Active: "; int count = 0; - for (auto& hook : hooks) - { - Path p (hook); - if (! p.is_directory ()) - { - auto name = p.name (); + for (auto& hook : hooks) { + Path p(hook); + if (!p.is_directory()) { + auto name = p.name(); - if (p.executable () && - (name.substr (0, 6) == "on-add" || - name.substr (0, 9) == "on-modify" || - name.substr (0, 9) == "on-launch" || - name.substr (0, 7) == "on-exit")) - { + if (p.executable() && + (name.substr(0, 6) == "on-add" || name.substr(0, 9) == "on-modify" || + name.substr(0, 9) == "on-launch" || name.substr(0, 7) == "on-exit")) { out << (count++ ? " " : ""); - out.width (longest); - out << std::left << name - << " (executable)" - << (p.is_link () ? " (symlink)" : "") - << '\n'; + out.width(longest); + out << std::left << name << " (executable)" << (p.is_link() ? " (symlink)" : "") << '\n'; } } } - if (! count) - out << '\n'; + if (!count) out << '\n'; out << " Inactive: "; count = 0; - for (auto& hook : hooks) - { - Path p (hook); - if (! p.is_directory ()) - { - auto name = p.name (); + for (auto& hook : hooks) { + Path p(hook); + if (!p.is_directory()) { + auto name = p.name(); - if (! p.executable () || - (name.substr (0, 6) != "on-add" && - name.substr (0, 9) != "on-modify" && - name.substr (0, 9) != "on-launch" && - name.substr (0, 7) != "on-exit")) - { + if (!p.executable() || + (name.substr(0, 6) != "on-add" && name.substr(0, 9) != "on-modify" && + name.substr(0, 9) != "on-launch" && name.substr(0, 7) != "on-exit")) { out << (count++ ? " " : ""); - out.width (longest); - out << std::left << name - << (p.executable () ? " (executable)" : " (not executable)") - << (p.is_link () ? " (symlink)" : "") - << ((name.substr (0, 6) == "on-add" || - name.substr (0, 9) == "on-modify" || - name.substr (0, 9) == "on-launch" || - name.substr (0, 7) == "on-exit") ? "" : "unrecognized hook name") + out.width(longest); + out << std::left << name << (p.executable() ? " (executable)" : " (not executable)") + << (p.is_link() ? " (symlink)" : "") + << ((name.substr(0, 6) == "on-add" || name.substr(0, 9) == "on-modify" || + name.substr(0, 9) == "on-launch" || name.substr(0, 7) == "on-exit") + ? "" + : "unrecognized hook name") << '\n'; } } } - if (! count) - out << '\n'; - } - else + if (!count) out << '\n'; + } else out << " (-none-)\n"; out << '\n'; // Verify UUIDs are all unique. - out << bold.colorize ("Tests") - << '\n'; + out << bold.colorize("Tests") << '\n'; // Report terminal dimensions. - out << " Terminal: " - << Context::getContext ().getWidth () - << 'x' - << Context::getContext ().getHeight () - << '\n'; - - // Scan tasks for duplicate UUIDs. - auto all = Context::getContext ().tdb2.all_tasks (); - std::map seen; - std::vector dups; - std::string uuid; - for (auto& i : all) - { - uuid = i.get ("uuid"); - if (seen.find (uuid) != seen.end ()) - dups.push_back (uuid); - else - seen[uuid] = 0; - } - - out << " Dups: " - << format ("Scanned {1} tasks for duplicate UUIDs:", all.size ()) - << '\n'; - - if (dups.size ()) - { - for (auto& d : dups) - out << " " << format ("Found duplicate {1}", d) << '\n'; - } - else - { - out << " No duplicates found\n"; - } + out << " Terminal: " << Context::getContext().getWidth() << 'x' + << Context::getContext().getHeight() << '\n'; // Check all the UUID references + auto all = Context::getContext().tdb2.all_tasks(); bool noBrokenRefs = true; - out << " Broken ref: " - << format ("Scanned {1} tasks for broken references:", all.size ()) - << '\n'; + out << " Broken ref: " << format("Scanned {1} tasks for broken references:", all.size()) << '\n'; - for (auto& task : all) - { + for (auto& task : all) { // Check dependencies - for (auto& uuid : task.getDependencyUUIDs ()) - { - if (! Context::getContext ().tdb2.has (uuid)) - { + for (auto& uuid : task.getDependencyUUIDs()) { + if (!Context::getContext().tdb2.has(uuid)) { out << " " - << format ("Task {1} depends on nonexistent task: {2}", task.get ("uuid"), uuid) - << '\n'; + << format("Task {1} depends on nonexistent task: {2}", task.get("uuid"), uuid) << '\n'; noBrokenRefs = false; } } // Check recurrence parent - auto parentUUID = task.get ("parent"); + auto parentUUID = task.get("parent"); - if (parentUUID != "" && ! Context::getContext ().tdb2.has (parentUUID)) - { + if (parentUUID != "" && !Context::getContext().tdb2.has(parentUUID)) { out << " " - << format ("Task {1} has nonexistent recurrence template {2}", task.get ("uuid"), parentUUID) + << format("Task {1} has nonexistent recurrence template {2}", task.get("uuid"), + parentUUID) << '\n'; noBrokenRefs = false; } } - if (noBrokenRefs) - out << " No broken references found\n"; + if (noBrokenRefs) out << " No broken references found\n"; out << '\n'; - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdDiagnostics.h b/src/commands/CmdDiagnostics.h index 0cb399e84..0d4adf62b 100644 --- a/src/commands/CmdDiagnostics.h +++ b/src/commands/CmdDiagnostics.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDDIAGNOSTICS #define INCLUDED_CMDDIAGNOSTICS -#include #include -class CmdDiagnostics : public Command -{ -public: - CmdDiagnostics (); - int execute (std::string&); +#include + +class CmdDiagnostics : public Command { + public: + CmdDiagnostics(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdDone.cpp b/src/commands/CmdDone.cpp index f579364ac..deb10a0dc 100644 --- a/src/commands/CmdDone.cpp +++ b/src/commands/CmdDone.cpp @@ -25,119 +25,109 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include +#include #include -#include +#include +#include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdDone::CmdDone () -{ - _keyword = "done"; - _usage = "task done "; - _description = "Marks the specified task as completed"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdDone::CmdDone() { + _keyword = "done"; + _usage = "task done "; + _description = "Marks the specified task as completed"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdDone::execute (std::string&) -{ +int CmdDone::execute(std::string&) { auto rc = 0; auto count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - std::vector modified; - for (auto& task : filtered) - { - Task before (task); + std::vector modified; + for (auto& task : filtered) { + Task before(task); - if (task.getStatus () == Task::pending || - task.getStatus () == Task::waiting) - { + if (task.getStatus() == Task::pending || task.getStatus() == Task::waiting) { // Complete the specified task. - std::string question = format ("Complete task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + std::string question = + format("Complete task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modAnnotate); - task.setStatus (Task::completed); - if (! task.has ("end")) - task.setAsNow ("end"); + task.modify(Task::modAnnotate); + task.setStatus(Task::completed); + if (!task.has("end")) task.setAsNow("end"); // Stop the task, if started. - if (task.has ("start")) - { - task.remove ("start"); - if (Context::getContext ().config.getBoolean ("journal.time")) - task.addAnnotation (Context::getContext ().config.get ("journal.time.stop.annotation")); + if (task.has("start")) { + task.remove("start"); + if (Context::getContext().config.getBoolean("journal.time")) + task.addAnnotation(Context::getContext().config.get("journal.time.stop.annotation")); } - if (permission (before.diff (task) + question, filtered.size ())) - { - updateRecurrenceMask (task); - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + updateRecurrenceMask(task); + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Completed task {1} '{2}'.", task); - feedback_unblocked (task); - dependencyChainOnComplete (task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task); + feedback_affected("Completed task {1} '{2}'.", task); + feedback_unblocked(task); + dependencyChainOnComplete(task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task); // Save unmodified task for potential nagging later modified.push_back(before); - } - else - { + } else { std::cout << "Task not completed.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } - } - else - { - std::cout << format ("Task {1} '{2}' is neither pending nor waiting.", - task.identifier (true), - task.get ("description")) + } else { + std::cout << format("Task {1} '{2}' is neither pending nor waiting.", task.identifier(true), + task.get("description")) << '\n'; rc = 1; } } - - nag (modified); - + + nag(modified); + // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Completed {1} task." : "Completed {1} tasks.", count); + feedback_affected(count == 1 ? "Completed {1} task." : "Completed {1} tasks.", count); return rc; } diff --git a/src/commands/CmdDone.h b/src/commands/CmdDone.h index 422a0e5e4..3aa4ce8b8 100644 --- a/src/commands/CmdDone.h +++ b/src/commands/CmdDone.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDDONE #define INCLUDED_CMDDONE -#include #include -class CmdDone : public Command -{ -public: - CmdDone (); - int execute (std::string&); +#include + +class CmdDone : public Command { + public: + CmdDone(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdDuplicate.cpp b/src/commands/CmdDuplicate.cpp index 46f1070e3..6ef1feb5d 100644 --- a/src/commands/CmdDuplicate.cpp +++ b/src/commands/CmdDuplicate.cpp @@ -25,120 +25,113 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include +#include #include #include -#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdDuplicate::CmdDuplicate () -{ - _keyword = "duplicate"; - _usage = "task duplicate "; - _description = "Duplicates the specified tasks"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdDuplicate::CmdDuplicate() { + _keyword = "duplicate"; + _usage = "task duplicate "; + _description = "Duplicates the specified tasks"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdDuplicate::execute (std::string&) -{ +int CmdDuplicate::execute(std::string&) { auto rc = 0; auto count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - for (auto& task : filtered) - { + for (auto& task : filtered) { // Duplicate the specified task. - Task dup (task); - dup.id = 0; // Reset, and TDB2::add will set. - dup.set ("uuid", uuid ()); // Needs a new UUID. - dup.remove ("start"); // Does not inherit start date. - dup.remove ("end"); // Does not inherit end date. - dup.remove ("entry"); // Does not inherit entry date. + Task dup(task); + dup.id = 0; // Reset, and TDB2::add will set. + dup.set("uuid", uuid()); // Needs a new UUID. + dup.remove("start"); // Does not inherit start date. + dup.remove("end"); // Does not inherit end date. + dup.remove("entry"); // Does not inherit entry date. // When duplicating a child task, downgrade it to a plain task. - if (dup.has ("parent")) - { - dup.remove ("parent"); - dup.remove ("recur"); - dup.remove ("until"); - dup.remove ("imask"); - std::cout << format ("Note: task {1} was a recurring task. The duplicated task is not.", task.identifier ()) - << '\n'; + if (dup.has("parent")) { + dup.remove("parent"); + dup.remove("recur"); + dup.remove("until"); + dup.remove("imask"); + std::cout << format("Note: task {1} was a recurring task. The duplicated task is not.", + task.identifier()) + << '\n'; } // When duplicating a parent task, create a new parent task. - else if (dup.getStatus () == Task::recurring) - { - dup.remove ("mask"); - std::cout << format ("Note: task {1} was a parent recurring task. The duplicated task is too.", task.identifier ()) - << '\n'; + else if (dup.getStatus() == Task::recurring) { + dup.remove("mask"); + std::cout << format( + "Note: task {1} was a parent recurring task. The duplicated task is too.", + task.identifier()) + << '\n'; } - dup.setStatus (Task::pending); // Does not inherit status. + dup.setStatus(Task::pending); // Does not inherit status. // Must occur after Task::recurring check. - dup.modify (Task::modAnnotate); + dup.modify(Task::modAnnotate); - if (permission (format ("Duplicate task {1} '{2}'?", - task.identifier (true), - task.get ("description")), - filtered.size ())) - { - Context::getContext ().tdb2.add (dup); + if (permission( + format("Duplicate task {1} '{2}'?", task.identifier(true), task.get("description")), + filtered.size())) { + Context::getContext().tdb2.add(dup); ++count; - feedback_affected ("Duplicated task {1} '{2}'.", task); + feedback_affected("Duplicated task {1} '{2}'.", task); - auto status = dup.getStatus (); - if (Context::getContext ().verbose ("new-id") && - (status == Task::pending || - status == Task::waiting)) - std::cout << format ("Created task {1}.\n", dup.id); + auto status = dup.getStatus(); + if (Context::getContext().verbose("new-id") && + (status == Task::pending || status == Task::waiting)) + std::cout << format("Created task {1}.\n", dup.id); - else if (Context::getContext ().verbose ("new-uuid") && - status != Task::recurring) - std::cout << format ("Created task {1}.\n", dup.get ("uuid")); + else if (Context::getContext().verbose("new-uuid") && status != Task::recurring) + std::cout << format("Created task {1}.\n", dup.get("uuid")); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task); - } - else - { + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task); + } else { std::cout << "Task not duplicated.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } } // Now list the project changes. for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Duplicated {1} task." : "Duplicated {1} tasks.", count); + feedback_affected(count == 1 ? "Duplicated {1} task." : "Duplicated {1} tasks.", count); return rc; } diff --git a/src/commands/CmdDuplicate.h b/src/commands/CmdDuplicate.h index 7360faccc..b05a53ab1 100644 --- a/src/commands/CmdDuplicate.h +++ b/src/commands/CmdDuplicate.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDDUPLICATE #define INCLUDED_CMDDUPLICATE -#include #include -class CmdDuplicate : public Command -{ -public: - CmdDuplicate (); - int execute (std::string&); +#include + +class CmdDuplicate : public Command { + public: + CmdDuplicate(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdEdit.cpp b/src/commands/CmdEdit.cpp index ca91527b0..b6535f1aa 100644 --- a/src/commands/CmdEdit.cpp +++ b/src/commands/CmdEdit.cpp @@ -25,106 +25,95 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include -#include -#include +#include #include #include -#include -#include #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include -#define STRING_EDIT_START_MOD "Start date modified." -#define STRING_EDIT_SCHED_MOD "Scheduled date modified." -#define STRING_EDIT_DUE_MOD "Due date modified." -#define STRING_EDIT_UNTIL_MOD "Until date modified." -#define STRING_EDIT_WAIT_MOD "Wait date modified." +#include +#include +#include +#include +#include +#include + +#define STRING_EDIT_START_MOD "Start date modified." +#define STRING_EDIT_SCHED_MOD "Scheduled date modified." +#define STRING_EDIT_DUE_MOD "Due date modified." +#define STRING_EDIT_UNTIL_MOD "Until date modified." +#define STRING_EDIT_WAIT_MOD "Wait date modified." const std::string CmdEdit::ANNOTATION_EDIT_MARKER = "\n "; //////////////////////////////////////////////////////////////////////////////// -CmdEdit::CmdEdit () -{ - _keyword = "edit"; - _usage = "task edit"; - _description = "Launches an editor to modify a task directly"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdEdit::CmdEdit() { + _keyword = "edit"; + _usage = "task edit"; + _description = "Launches an editor to modify a task directly"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// // Introducing the Silver Bullet. This feature is the catch-all fixative for // various other ills. This is like opening up the hood and going in with a // wrench. To be used sparingly. -int CmdEdit::execute (std::string&) -{ +int CmdEdit::execute(std::string&) { // Filter the tasks. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - if (! filtered.size ()) - { - Context::getContext ().footnote ("No matches."); + if (!filtered.size()) { + Context::getContext().footnote("No matches."); return 1; } - unsigned int bulk = Context::getContext ().config.getInteger ("bulk"); + unsigned int bulk = Context::getContext().config.getInteger("bulk"); // If we are editing more than "bulk" tasks, ask for confirmation. // Bulk = 0 denotes infinite bulk. - if ((filtered.size () > bulk) && (bulk != 0)) - if (! confirm (format ("Do you wish to manually edit {1} tasks?", filtered.size ()))) - return 2; + if ((filtered.size() > bulk) && (bulk != 0)) + if (!confirm(format("Do you wish to manually edit {1} tasks?", filtered.size()))) return 2; // Find number of matching tasks. - for (auto& task : filtered) - { - auto result = editFile (task); + for (auto& task : filtered) { + auto result = editFile(task); if (result == CmdEdit::editResult::error) break; else if (result == CmdEdit::editResult::changes) - Context::getContext ().tdb2.modify (task); + Context::getContext().tdb2.modify(task); } return 0; } //////////////////////////////////////////////////////////////////////////////// -std::string CmdEdit::findValue ( - const std::string& text, - const std::string& name) -{ - auto found = text.find (name); - if (found != std::string::npos) - { - auto eol = text.find ('\n', found + 1); - if (eol != std::string::npos) - { - std::string value = text.substr ( - found + name.length (), - eol - (found + name.length ())); +std::string CmdEdit::findValue(const std::string& text, const std::string& name) { + auto found = text.find(name); + if (found != std::string::npos) { + auto eol = text.find('\n', found + 1); + if (eol != std::string::npos) { + std::string value = text.substr(found + name.length(), eol - (found + name.length())); - return Lexer::trim (value, "\t "); + return Lexer::trim(value, "\t "); } } @@ -132,47 +121,34 @@ std::string CmdEdit::findValue ( } //////////////////////////////////////////////////////////////////////////////// -std::string CmdEdit::findMultilineValue ( - const std::string& text, - const std::string& startMarker, - const std::string& endMarker) -{ - auto start = text.find (startMarker); - if (start != std::string::npos) - { - auto end = text.find (endMarker, start); - if (end != std::string::npos) - { - std::string value = text.substr (start + startMarker.length (), - end - (start + startMarker.length ())); - return Lexer::trim (value, "\\\t "); +std::string CmdEdit::findMultilineValue(const std::string& text, const std::string& startMarker, + const std::string& endMarker) { + auto start = text.find(startMarker); + if (start != std::string::npos) { + auto end = text.find(endMarker, start); + if (end != std::string::npos) { + std::string value = + text.substr(start + startMarker.length(), end - (start + startMarker.length())); + return Lexer::trim(value, "\\\t "); } } return ""; } //////////////////////////////////////////////////////////////////////////////// -std::vector CmdEdit::findValues ( - const std::string& text, - const std::string& name) -{ - std::vector results; +std::vector CmdEdit::findValues(const std::string& text, const std::string& name) { + std::vector results; std::string::size_type found = 0; - while (found != std::string::npos) - { - found = text.find (name, found + 1); - if (found != std::string::npos) - { - auto eol = text.find ('\n', found + 1); - if (eol != std::string::npos) - { - auto value = text.substr ( - found + name.length (), - eol - (found + name.length ())); + while (found != std::string::npos) { + found = text.find(name, found + 1); + if (found != std::string::npos) { + auto eol = text.find('\n', found + 1); + if (eol != std::string::npos) { + auto value = text.substr(found + name.length(), eol - (found + name.length())); found = eol - 1; - results.push_back (Lexer::trim (value, "\t ")); + results.push_back(Lexer::trim(value, "\t ")); } } } @@ -181,35 +157,26 @@ std::vector CmdEdit::findValues ( } //////////////////////////////////////////////////////////////////////////////// -std::string CmdEdit::formatDate ( - Task& task, - const std::string& attribute, - const std::string& dateformat) -{ - auto value = task.get (attribute); - if (value.length ()) - value = Datetime (value).toString (dateformat); +std::string CmdEdit::formatDate(Task& task, const std::string& attribute, + const std::string& dateformat) { + auto value = task.get(attribute); + if (value.length()) value = Datetime(value).toString(dateformat); return value; } //////////////////////////////////////////////////////////////////////////////// -std::string CmdEdit::formatDuration ( - Task& task, - const std::string& attribute) -{ - auto value = task.get (attribute); - if (value.length ()) - value = Duration (value).formatISO (); +std::string CmdEdit::formatDuration(Task& task, const std::string& attribute) { + auto value = task.get(attribute); + if (value.length()) value = Duration(value).formatISO(); return value; } //////////////////////////////////////////////////////////////////////////////// -std::string CmdEdit::formatTask (Task task, const std::string& dateformat) -{ +std::string CmdEdit::formatTask(Task task, const std::string& dateformat) { std::stringstream before; - auto verbose = Context::getContext ().verbose ("edit"); + auto verbose = Context::getContext().verbose("edit"); if (verbose) before << "# The 'task edit' command allows you to modify all aspects of a task\n" @@ -230,408 +197,320 @@ std::string CmdEdit::formatTask (Task task, const std::string& dateformat) before << "# Name Editable details\n" << "# ----------------- ----------------------------------------------------\n" - << "# ID: " << task.id << '\n' - << "# UUID: " << task.get ("uuid") << '\n' - << "# Status: " << Lexer::ucFirst (Task::statusToText (task.getStatus ())) << '\n' - << "# Mask: " << task.get ("mask") << '\n' - << "# iMask: " << task.get ("imask") << '\n' - << " Project: " << task.get ("project") << '\n'; + << "# ID: " << task.id << '\n' + << "# UUID: " << task.get("uuid") << '\n' + << "# Status: " << Lexer::ucFirst(Task::statusToText(task.getStatus())) << '\n' + << "# Mask: " << task.get("mask") << '\n' + << "# iMask: " << task.get("imask") << '\n' + << " Project: " << task.get("project") << '\n'; + + if (verbose) before << "# Separate the tags with spaces, like this: tag1 tag2\n"; + + before << " Tags: " << join(" ", task.getTags()) << '\n' + << " Description: " << task.get("description") << '\n' + << " Created: " << formatDate(task, "entry", dateformat) << '\n' + << " Started: " << formatDate(task, "start", dateformat) << '\n' + << " Ended: " << formatDate(task, "end", dateformat) << '\n' + << " Scheduled: " << formatDate(task, "scheduled", dateformat) << '\n' + << " Due: " << formatDate(task, "due", dateformat) << '\n' + << " Until: " << formatDate(task, "until", dateformat) << '\n' + << " Recur: " << task.get("recur") << '\n' + << " Wait until: " << formatDate(task, "wait", dateformat) << '\n' + << "# Modified: " << formatDate(task, "modified", dateformat) << '\n' + << " Parent: " << task.get("parent") << '\n'; if (verbose) - before << "# Separate the tags with spaces, like this: tag1 tag2\n"; + before + << "# Annotations look like this: -- and there can be any number of them.\n" + "# The ' -- ' separator between the date and text field should not be removed.\n" + "# Multiline annotations need to be indented up to (" + << ANNOTATION_EDIT_MARKER.length() - 1 + << " spaces).\n" + "# A \"blank slot\" for adding an annotation follows for your convenience.\n"; - before << " Tags: " << join (" ", task.getTags ()) << '\n' - << " Description: " << task.get ("description") << '\n' - << " Created: " << formatDate (task, "entry", dateformat) << '\n' - << " Started: " << formatDate (task, "start", dateformat) << '\n' - << " Ended: " << formatDate (task, "end", dateformat) << '\n' - << " Scheduled: " << formatDate (task, "scheduled", dateformat) << '\n' - << " Due: " << formatDate (task, "due", dateformat) << '\n' - << " Until: " << formatDate (task, "until", dateformat) << '\n' - << " Recur: " << task.get ("recur") << '\n' - << " Wait until: " << formatDate (task, "wait", dateformat) << '\n' - << "# Modified: " << formatDate (task, "modified", dateformat) << '\n' - << " Parent: " << task.get ("parent") << '\n'; - - if (verbose) - before << "# Annotations look like this: -- and there can be any number of them.\n" - "# The ' -- ' separator between the date and text field should not be removed.\n" - "# Multiline annotations need to be indented up to (" << ANNOTATION_EDIT_MARKER.length () - 1 << " spaces).\n" - "# A \"blank slot\" for adding an annotation follows for your convenience.\n"; - - for (auto& anno : task.getAnnotations ()) - { - Datetime dt (strtoll (anno.first.substr (11).c_str (), nullptr, 10)); - before << " Annotation: " << dt.toString (dateformat) - << " -- " << str_replace (anno.second, "\n", ANNOTATION_EDIT_MARKER) << '\n'; + for (auto& anno : task.getAnnotations()) { + Datetime dt(strtoll(anno.first.substr(11).c_str(), nullptr, 10)); + before << " Annotation: " << dt.toString(dateformat) << " -- " + << str_replace(anno.second, "\n", ANNOTATION_EDIT_MARKER) << '\n'; } Datetime now; - before << " Annotation: " << now.toString (dateformat) << " -- \n"; + before << " Annotation: " << now.toString(dateformat) << " -- \n"; // Add dependencies here. - auto dependencies = task.getDependencyUUIDs (); + auto dependencies = task.getDependencyUUIDs(); std::stringstream allDeps; - for (unsigned int i = 0; i < dependencies.size (); ++i) - { - if (i) - allDeps << ","; + for (unsigned int i = 0; i < dependencies.size(); ++i) { + if (i) allDeps << ","; Task t; - Context::getContext ().tdb2.get (dependencies[i], t); - if (t.getStatus () == Task::pending || - t.getStatus () == Task::waiting) + Context::getContext().tdb2.get(dependencies[i], t); + if (t.getStatus() == Task::pending || t.getStatus() == Task::waiting) allDeps << t.id; else allDeps << dependencies[i]; } if (verbose) - before << "# Dependencies should be a comma-separated list of task IDs/UUIDs or ID ranges, with no spaces.\n"; + before << "# Dependencies should be a comma-separated list of task IDs/UUIDs or ID ranges, " + "with no spaces.\n"; - before << " Dependencies: " << allDeps.str () << '\n'; + before << " Dependencies: " << allDeps.str() << '\n'; // UDAs - std::vector udas; - for (auto& col : Context::getContext ().columns) - if (Context::getContext ().config.get ("uda." + col.first + ".type") != "") - udas.push_back (col.first); + std::vector udas; + for (auto& col : Context::getContext().columns) + if (Context::getContext().config.get("uda." + col.first + ".type") != "") + udas.push_back(col.first); - if (udas.size ()) - { + if (udas.size()) { before << "# User Defined Attributes\n"; - std::sort (udas.begin (), udas.end ()); - for (auto& uda : udas) - { - int pad = 13 - uda.length (); + std::sort(udas.begin(), udas.end()); + for (auto& uda : udas) { + int pad = 13 - uda.length(); std::string padding = ""; - if (pad > 0) - padding = std::string (pad, ' '); + if (pad > 0) padding = std::string(pad, ' '); - std::string type = Context::getContext ().config.get ("uda." + uda + ".type"); - if (type == "string" || type == "numeric") - { - auto value = task.get (uda); - if (type == "string") - value = json::encode (value); + std::string type = Context::getContext().config.get("uda." + uda + ".type"); + if (type == "string" || type == "numeric") { + auto value = task.get(uda); + if (type == "string") value = json::encode(value); before << " UDA " << uda << ": " << padding << value << '\n'; - } - else if (type == "date") - before << " UDA " << uda << ": " << padding << formatDate (task, uda, dateformat) << '\n'; + } else if (type == "date") + before << " UDA " << uda << ": " << padding << formatDate(task, uda, dateformat) << '\n'; else if (type == "duration") - before << " UDA " << uda << ": " << padding << formatDuration (task, uda) << '\n'; + before << " UDA " << uda << ": " << padding << formatDuration(task, uda) << '\n'; } } // UDA orphans - auto orphans = task.getUDAOrphans (); - if (orphans.size ()) - { + auto orphans = task.getUDAOrphans(); + if (orphans.size()) { before << "# User Defined Attribute Orphans\n"; - std::sort (orphans.begin (), orphans.end ()); - for (auto& orphan : orphans) - { - int pad = 6 - orphan.length (); + std::sort(orphans.begin(), orphans.end()); + for (auto& orphan : orphans) { + int pad = 6 - orphan.length(); std::string padding = ""; - if (pad > 0) - padding = std::string (pad, ' '); + if (pad > 0) padding = std::string(pad, ' '); - before << " UDA Orphan " << orphan << ": " << padding << task.get (orphan) << '\n'; + before << " UDA Orphan " << orphan << ": " << padding << task.get(orphan) << '\n'; } } before << "# End\n"; - return before.str (); + return before.str(); } //////////////////////////////////////////////////////////////////////////////// -void CmdEdit::parseTask (Task& task, const std::string& after, const std::string& dateformat) -{ +void CmdEdit::parseTask(Task& task, const std::string& after, const std::string& dateformat) { // project - auto value = findValue (after, "\n Project:"); - if (task.get ("project") != value) - { - if (value != "") - { - Context::getContext ().footnote ("Project modified."); - task.set ("project", value); - } - else - { - Context::getContext ().footnote ("Project deleted."); - task.remove ("project"); + auto value = findValue(after, "\n Project:"); + if (task.get("project") != value) { + if (value != "") { + Context::getContext().footnote("Project modified."); + task.set("project", value); + } else { + Context::getContext().footnote("Project deleted."); + task.remove("project"); } } // tags - value = findValue (after, "\n Tags:"); - task.remove ("tags"); - task.setTags (split (value, ' ')); + value = findValue(after, "\n Tags:"); + task.setTags(split(value, ' ')); // description. - value = findMultilineValue (after, "\n Description:", "\n Created:"); - if (task.get ("description") != value) - { - if (value != "") - { - Context::getContext ().footnote ("Description modified."); - task.set ("description", value); - } - else - throw std::string ("Cannot remove description."); + value = findMultilineValue(after, "\n Description:", "\n Created:"); + if (task.get("description") != value) { + if (value != "") { + Context::getContext().footnote("Description modified."); + task.set("description", value); + } else + throw std::string("Cannot remove description."); } // entry - value = findValue (after, "\n Created:"); - if (value != "") - { - if (value != formatDate (task, "entry", dateformat)) - { - Context::getContext ().footnote ("Creation date modified."); - task.set ("entry", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Created:"); + if (value != "") { + if (value != formatDate(task, "entry", dateformat)) { + Context::getContext().footnote("Creation date modified."); + task.set("entry", Datetime(value, dateformat).toEpochString()); } - } - else - throw std::string ("Cannot remove creation date."); + } else + throw std::string("Cannot remove creation date."); // start - value = findValue (after, "\n Started:"); - if (value != "") - { - if (task.get ("start") != "") - { - if (value != formatDate (task, "start", dateformat)) - { - Context::getContext ().footnote (STRING_EDIT_START_MOD); - task.set ("start", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Started:"); + if (value != "") { + if (task.get("start") != "") { + if (value != formatDate(task, "start", dateformat)) { + Context::getContext().footnote(STRING_EDIT_START_MOD); + task.set("start", Datetime(value, dateformat).toEpochString()); } + } else { + Context::getContext().footnote(STRING_EDIT_START_MOD); + task.set("start", Datetime(value, dateformat).toEpochString()); } - else - { - Context::getContext ().footnote (STRING_EDIT_START_MOD); - task.set ("start", Datetime (value, dateformat).toEpochString ()); - } - } - else - { - if (task.get ("start") != "") - { - Context::getContext ().footnote ("Start date removed."); - task.remove ("start"); + } else { + if (task.get("start") != "") { + Context::getContext().footnote("Start date removed."); + task.remove("start"); } } // end - value = findValue (after, "\n Ended:"); - if (value != "") - { - if (task.get ("end") != "") - { - if (value != formatDate (task, "end", dateformat)) - { - Context::getContext ().footnote ("End date modified."); - task.set ("end", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Ended:"); + if (value != "") { + if (task.get("end") != "") { + if (value != formatDate(task, "end", dateformat)) { + Context::getContext().footnote("End date modified."); + task.set("end", Datetime(value, dateformat).toEpochString()); } - } - else if (task.getStatus () != Task::deleted) - throw std::string ("Cannot set a done date on a pending task."); - } - else - { - if (task.get ("end") != "") - { - Context::getContext ().footnote ("End date removed."); - task.setStatus (Task::pending); - task.remove ("end"); + } else if (task.getStatus() != Task::deleted) + throw std::string("Cannot set a done date on a pending task."); + } else { + if (task.get("end") != "") { + Context::getContext().footnote("End date removed."); + task.setStatus(Task::pending); + task.remove("end"); } } // scheduled - value = findValue (after, "\n Scheduled:"); - if (value != "") - { - if (task.get ("scheduled") != "") - { - if (value != formatDate (task, "scheduled", dateformat)) - { - Context::getContext ().footnote (STRING_EDIT_SCHED_MOD); - task.set ("scheduled", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Scheduled:"); + if (value != "") { + if (task.get("scheduled") != "") { + if (value != formatDate(task, "scheduled", dateformat)) { + Context::getContext().footnote(STRING_EDIT_SCHED_MOD); + task.set("scheduled", Datetime(value, dateformat).toEpochString()); } + } else { + Context::getContext().footnote(STRING_EDIT_SCHED_MOD); + task.set("scheduled", Datetime(value, dateformat).toEpochString()); } - else - { - Context::getContext ().footnote (STRING_EDIT_SCHED_MOD); - task.set ("scheduled", Datetime (value, dateformat).toEpochString ()); - } - } - else - { - if (task.get ("scheduled") != "") - { - Context::getContext ().footnote ("Scheduled date removed."); - task.remove ("scheduled"); + } else { + if (task.get("scheduled") != "") { + Context::getContext().footnote("Scheduled date removed."); + task.remove("scheduled"); } } // due - value = findValue (after, "\n Due:"); - if (value != "") - { - if (task.get ("due") != "") - { - if (value != formatDate (task, "due", dateformat)) - { - Context::getContext ().footnote (STRING_EDIT_DUE_MOD); - task.set ("due", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Due:"); + if (value != "") { + if (task.get("due") != "") { + if (value != formatDate(task, "due", dateformat)) { + Context::getContext().footnote(STRING_EDIT_DUE_MOD); + task.set("due", Datetime(value, dateformat).toEpochString()); } + } else { + Context::getContext().footnote(STRING_EDIT_DUE_MOD); + task.set("due", Datetime(value, dateformat).toEpochString()); } - else - { - Context::getContext ().footnote (STRING_EDIT_DUE_MOD); - task.set ("due", Datetime (value, dateformat).toEpochString ()); - } - } - else - { - if (task.get ("due") != "") - { - if (task.getStatus () == Task::recurring || - task.get ("parent") != "") - { - Context::getContext ().footnote ("Cannot remove a due date from a recurring task."); - } - else - { - Context::getContext ().footnote ("Due date removed."); - task.remove ("due"); + } else { + if (task.get("due") != "") { + if (task.getStatus() == Task::recurring || task.get("parent") != "") { + Context::getContext().footnote("Cannot remove a due date from a recurring task."); + } else { + Context::getContext().footnote("Due date removed."); + task.remove("due"); } } } // until - value = findValue (after, "\n Until:"); - if (value != "") - { - if (task.get ("until") != "") - { - if (value != formatDate (task, "until", dateformat)) - { - Context::getContext ().footnote (STRING_EDIT_UNTIL_MOD); - task.set ("until", Datetime (value, dateformat).toEpochString ()); + value = findValue(after, "\n Until:"); + if (value != "") { + if (task.get("until") != "") { + if (value != formatDate(task, "until", dateformat)) { + Context::getContext().footnote(STRING_EDIT_UNTIL_MOD); + task.set("until", Datetime(value, dateformat).toEpochString()); } + } else { + Context::getContext().footnote(STRING_EDIT_UNTIL_MOD); + task.set("until", Datetime(value, dateformat).toEpochString()); } - else - { - Context::getContext ().footnote (STRING_EDIT_UNTIL_MOD); - task.set ("until", Datetime (value, dateformat).toEpochString ()); - } - } - else - { - if (task.get ("until") != "") - { - Context::getContext ().footnote ("Until date removed."); - task.remove ("until"); + } else { + if (task.get("until") != "") { + Context::getContext().footnote("Until date removed."); + task.remove("until"); } } // recur - value = findValue (after, "\n Recur:"); - if (value != task.get ("recur")) - { - if (value != "") - { + value = findValue(after, "\n Recur:"); + if (value != task.get("recur")) { + if (value != "") { Duration p; std::string::size_type idx = 0; - if (p.parse (value, idx)) - { - Context::getContext ().footnote ("Recurrence modified."); - if (task.get ("due") != "") - { - task.set ("recur", value); - task.setStatus (Task::recurring); - } - else - throw std::string ("A recurring task must have a due date."); - } - else - throw std::string ("Not a valid recurrence duration."); - } - else - { - Context::getContext ().footnote ("Recurrence removed."); - task.setStatus (Task::pending); - task.remove ("recur"); - task.remove ("until"); - task.remove ("mask"); - task.remove ("imask"); + if (p.parse(value, idx)) { + Context::getContext().footnote("Recurrence modified."); + if (task.get("due") != "") { + task.set("recur", value); + task.setStatus(Task::recurring); + } else + throw std::string("A recurring task must have a due date."); + } else + throw std::string("Not a valid recurrence duration."); + } else { + Context::getContext().footnote("Recurrence removed."); + task.setStatus(Task::pending); + task.remove("recur"); + task.remove("until"); + task.remove("mask"); + task.remove("imask"); } } // wait - value = findValue (after, "\n Wait until:"); - if (value != "") - { - if (task.get ("wait") != "") - { - if (value != formatDate (task, "wait", dateformat)) - { - Context::getContext ().footnote (STRING_EDIT_WAIT_MOD); - task.set ("wait", Datetime (value, dateformat).toEpochString ()); - task.setStatus (Task::waiting); + value = findValue(after, "\n Wait until:"); + if (value != "") { + if (task.get("wait") != "") { + if (value != formatDate(task, "wait", dateformat)) { + Context::getContext().footnote(STRING_EDIT_WAIT_MOD); + task.set("wait", Datetime(value, dateformat).toEpochString()); + task.setStatus(Task::waiting); } + } else { + Context::getContext().footnote(STRING_EDIT_WAIT_MOD); + task.set("wait", Datetime(value, dateformat).toEpochString()); + task.setStatus(Task::waiting); } - else - { - Context::getContext ().footnote (STRING_EDIT_WAIT_MOD); - task.set ("wait", Datetime (value, dateformat).toEpochString ()); - task.setStatus (Task::waiting); - } - } - else - { - if (task.get ("wait") != "") - { - Context::getContext ().footnote ("Wait date removed."); - task.remove ("wait"); - task.setStatus (Task::pending); + } else { + if (task.get("wait") != "") { + Context::getContext().footnote("Wait date removed."); + task.remove("wait"); + task.setStatus(Task::pending); } } // parent - value = findValue (after, "\n Parent:"); - if (value != task.get ("parent")) - { - if (value != "") - { - Context::getContext ().footnote ("Parent UUID modified."); - task.set ("parent", value); - } - else - { - Context::getContext ().footnote ("Parent UUID removed."); - task.remove ("parent"); + value = findValue(after, "\n Parent:"); + if (value != task.get("parent")) { + if (value != "") { + Context::getContext().footnote("Parent UUID modified."); + task.set("parent", value); + } else { + Context::getContext().footnote("Parent UUID removed."); + task.remove("parent"); } } // Annotations - std::map annotations; + std::map annotations; std::string::size_type found = 0; - while ((found = after.find ("\n Annotation:", found)) != std::string::npos) - { + while ((found = after.find("\n Annotation:", found)) != std::string::npos) { found += 14; // Length of "\n Annotation:". auto eol = found; - while ((eol = after.find ('\n', ++eol)) != std::string::npos) - if (after.substr (eol, ANNOTATION_EDIT_MARKER.length ()) != ANNOTATION_EDIT_MARKER) - break; + while ((eol = after.find('\n', ++eol)) != std::string::npos) + if (after.substr(eol, ANNOTATION_EDIT_MARKER.length()) != ANNOTATION_EDIT_MARKER) break; - if (eol != std::string::npos) - { - auto value = Lexer::trim (str_replace (after.substr (found, eol - found), ANNOTATION_EDIT_MARKER, "\n"), "\t "); - auto gap = value.find (" -- "); - if (gap != std::string::npos) - { + if (eol != std::string::npos) { + auto value = Lexer::trim( + str_replace(after.substr(found, eol - found), ANNOTATION_EDIT_MARKER, "\n"), "\t "); + auto gap = value.find(" -- "); + if (gap != std::string::npos) { // TODO keeping the initial dates even if dateformat approximates them // is complex as finding the correspondence between each original line // and edited line may be impossible (bug #705). It would be simpler if @@ -639,228 +518,190 @@ void CmdEdit::parseTask (Task& task, const std::string& after, const std::string // for each line: if the annotation is the same, then it is copied; if // the annotation is modified, then its original date may be kept; and // if there is no corresponding id, then a new unique date is created). - Datetime when (value.substr (0, gap), dateformat); + Datetime when(value.substr(0, gap), dateformat); // If the map already contains an annotation for a given timestamp // we need to increment until we find an unused key - int timestamp = (int) when.toEpoch (); + int timestamp = (int)when.toEpoch(); std::stringstream name; - do - { - name.str (""); // Clear + do { + name.str(""); // Clear name << "annotation_" << timestamp; timestamp++; - } - while (annotations.find (name.str ()) != annotations.end ()); + } while (annotations.find(name.str()) != annotations.end()); - auto text = Lexer::trim (value.substr (gap + 4), "\t "); - annotations.emplace (name.str (), text); + auto text = Lexer::trim(value.substr(gap + 4), "\t "); + annotations.emplace(name.str(), text); } } } - task.setAnnotations (annotations); + task.setAnnotations(annotations); // Dependencies - value = findValue (after, "\n Dependencies:"); - auto dependencies = split (value, ','); + value = findValue(after, "\n Dependencies:"); + auto dependencies = split(value, ','); - for (auto& dep : task.getDependencyUUIDs ()) - task.removeDependency (dep); - for (auto& dep : dependencies) - { - if (dep.length () >= 7) - task.addDependency (dep); + for (auto& dep : task.getDependencyUUIDs()) task.removeDependency(dep); + for (auto& dep : dependencies) { + if (dep.length() >= 7) + task.addDependency(dep); else - task.addDependency ((int) strtol (dep.c_str (), nullptr, 10)); + task.addDependency((int)strtol(dep.c_str(), nullptr, 10)); } // UDAs - for (auto& col : Context::getContext ().columns) - { - auto type = Context::getContext ().config.get ("uda." + col.first + ".type"); - if (type != "") - { - auto value = findValue (after, "\n UDA " + col.first + ":"); - if (type == "string") - value = json::decode (value); - if ((task.get (col.first) != value) && (type != "date" || - (task.get (col.first) != Datetime (value, dateformat).toEpochString ())) && - (type != "duration" || - (task.get (col.first) != Duration (value).toString ()))) - { - if (value != "") - { - Context::getContext ().footnote (format ("UDA {1} modified.", col.first)); + for (auto& col : Context::getContext().columns) { + auto type = Context::getContext().config.get("uda." + col.first + ".type"); + if (type != "") { + auto value = findValue(after, "\n UDA " + col.first + ":"); + if (type == "string") value = json::decode(value); + if ((task.get(col.first) != value) && + (type != "date" || + (task.get(col.first) != Datetime(value, dateformat).toEpochString())) && + (type != "duration" || (task.get(col.first) != Duration(value).toString()))) { + if (value != "") { + Context::getContext().footnote(format("UDA {1} modified.", col.first)); - if (type == "string") - { - task.set (col.first, value); - } - else if (type == "numeric") - { - Pig pig (value); + if (type == "string") { + task.set(col.first, value); + } else if (type == "numeric") { + Pig pig(value); double d; - if (pig.getNumber (d) && - pig.eos ()) - task.set (col.first, value); + if (pig.getNumber(d) && pig.eos()) + task.set(col.first, value); else - throw format ("The value '{1}' is not a valid numeric value.", value); + throw format("The value '{1}' is not a valid numeric value.", value); + } else if (type == "date") { + task.set(col.first, Datetime(value, dateformat).toEpochString()); + } else if (type == "duration") { + task.set(col.first, Duration(value).toTime_t()); } - else if (type == "date") - { - task.set (col.first, Datetime (value, dateformat).toEpochString ()); - } - else if (type == "duration") - { - task.set (col.first, Duration (value).toTime_t ()); - } - } - else - { - Context::getContext ().footnote (format ("UDA {1} deleted.", col.first)); - task.remove (col.first); + } else { + Context::getContext().footnote(format("UDA {1} deleted.", col.first)); + task.remove(col.first); } } } } // UDA orphans - for (auto& orphan : findValues (after, "\n UDA Orphan ")) - { - auto colon = orphan.find (':'); - if (colon != std::string::npos) - { - std::string name = Lexer::trim (orphan.substr (0, colon), "\t "); - std::string value = Lexer::trim (orphan.substr (colon + 1), "\t "); + for (auto& orphan : findValues(after, "\n UDA Orphan ")) { + auto colon = orphan.find(':'); + if (colon != std::string::npos) { + std::string name = Lexer::trim(orphan.substr(0, colon), "\t "); + std::string value = Lexer::trim(orphan.substr(colon + 1), "\t "); if (value != "") - task.set (name, value); + task.set(name, value); else - task.remove (name); + task.remove(name); } } } //////////////////////////////////////////////////////////////////////////////// -CmdEdit::editResult CmdEdit::editFile (Task& task) -{ +CmdEdit::editResult CmdEdit::editFile(Task& task) { // Check for file permissions. - Directory location (Context::getContext ().config.get ("data.location")); - if (! location.writable ()) - throw std::string ("Your data.location directory is not writable."); + Directory location(Context::getContext().config.get("data.location")); + if (!location.writable()) throw std::string("Your data.location directory is not writable."); // Create a temp file name in data.location. std::stringstream file; - file << "task." << task.get ("uuid").substr (0, 8) << ".task"; + file << "task." << task.get("uuid").substr(0, 8) << ".task"; // Determine the output date format, which uses a hierarchy of definitions. // rc.dateformat.edit // rc.dateformat - auto dateformat = Context::getContext ().config.get ("dateformat.edit"); - if (dateformat == "") - dateformat = Context::getContext ().config.get ("dateformat"); + auto dateformat = Context::getContext().config.get("dateformat.edit"); + if (dateformat == "") dateformat = Context::getContext().config.get("dateformat"); - // Change directory for the editor - auto current_dir = Directory::cwd (); - int ignored = chdir (location._data.c_str ()); - ++ignored; // Keep compiler quiet. + // Change directory for the editor, doing nothing on error. + auto current_dir = Directory::cwd(); + chdir(location._data.c_str()); // Check if the file already exists, if so, bail out - Path filepath = Path (file.str ()); - if (filepath.exists ()) - throw std::string ("Task is already being edited."); + Path filepath = Path(file.str()); + if (filepath.exists()) throw std::string("Task is already being edited."); // Format the contents, T -> text, write to a file. - auto before = formatTask (task, dateformat); + auto before = formatTask(task, dateformat); auto before_orig = before; - File::write (file.str (), before); + File::write(file.str(), before); // Determine correct editor: .taskrc:editor > $VISUAL > $EDITOR > vi - auto editor = Context::getContext ().config.get ("editor"); - char* peditor = getenv ("VISUAL"); - if (editor == "" && peditor) editor = std::string (peditor); - peditor = getenv ("EDITOR"); - if (editor == "" && peditor) editor = std::string (peditor); + auto editor = Context::getContext().config.get("editor"); + char* peditor = getenv("VISUAL"); + if (editor == "" && peditor) editor = std::string(peditor); + peditor = getenv("EDITOR"); + if (editor == "" && peditor) editor = std::string(peditor); if (editor == "") editor = "vi"; // Complete the command line. editor += ' '; - editor += '"' + file.str () + '"'; + editor += '"' + file.str() + '"'; ARE_THESE_REALLY_HARMFUL: - bool changes = false; // No changes made. + bool changes = false; // No changes made. // Launch the editor. - std::cout << format ("Launching '{1}' now...\n", editor); - int exitcode = system (editor.c_str ()); + std::cout << format("Launching '{1}' now...\n", editor); + int exitcode = system(editor.c_str()); auto captured_errno = errno; if (0 == exitcode) std::cout << "Editing complete.\n"; - else - { - std::cout << format ("Editing failed with exit code {1}.\n", exitcode); - if (-1 == exitcode) - std::cout << std::strerror (captured_errno) << '\n'; - File::remove (file.str ()); + else { + std::cout << format("Editing failed with exit code {1}.\n", exitcode); + if (-1 == exitcode) std::cout << std::strerror(captured_errno) << '\n'; + File::remove(file.str()); return CmdEdit::editResult::error; } // Slurp file. std::string after; - File::read (file.str (), after); + File::read(file.str(), after); // Update task based on what can be parsed back out of the file, but only // if changes were made. - if (before_orig != after) - { + if (before_orig != after) { std::cout << "Edits were detected.\n"; std::string problem = ""; auto oops = false; - try - { - parseTask (task, after, dateformat); + try { + parseTask(task, after, dateformat); } - catch (const std::string& e) - { + catch (const std::string& e) { problem = e; oops = true; } - if (oops) - { + if (oops) { std::cerr << "Error: " << problem << '\n'; - File::remove (file.str()); + File::remove(file.str()); - if (confirm ("Taskwarrior couldn't handle your edits. Would you like to try again?")) - { + if (confirm("Taskwarrior couldn't handle your edits. Would you like to try again?")) { // Preserve the edits. before = after; - File::write (file.str (), before); + File::write(file.str(), before); goto ARE_THESE_REALLY_HARMFUL; } - } - else + } else changes = true; - } - else - { + } else { std::cout << "No edits were detected.\n"; changes = false; } // Cleanup. - File::remove (file.str ()); - ignored = chdir (current_dir.c_str ()); - return changes - ? CmdEdit::editResult::changes - : CmdEdit::editResult::nochanges; + File::remove(file.str()); + chdir(current_dir.c_str()); + return changes ? CmdEdit::editResult::changes : CmdEdit::editResult::nochanges; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdEdit.h b/src/commands/CmdEdit.h index 9d2272594..2d5fbd3d7 100644 --- a/src/commands/CmdEdit.h +++ b/src/commands/CmdEdit.h @@ -27,26 +27,26 @@ #ifndef INCLUDED_CMDEDIT #define INCLUDED_CMDEDIT -#include #include #include -class CmdEdit : public Command -{ -public: - CmdEdit (); - int execute (std::string&); +#include -private: - std::string findValue (const std::string&, const std::string&); - std::string findMultilineValue (const std::string&, const std::string&, const std::string&); - std::vector findValues (const std::string&, const std::string&); - std::string formatDate (Task&, const std::string&, const std::string&); - std::string formatDuration (Task&, const std::string&); - std::string formatTask (Task, const std::string&); - void parseTask (Task&, const std::string&, const std::string&); +class CmdEdit : public Command { + public: + CmdEdit(); + int execute(std::string&); + + private: + std::string findValue(const std::string&, const std::string&); + std::string findMultilineValue(const std::string&, const std::string&, const std::string&); + std::vector findValues(const std::string&, const std::string&); + std::string formatDate(Task&, const std::string&, const std::string&); + std::string formatDuration(Task&, const std::string&); + std::string formatTask(Task, const std::string&); + void parseTask(Task&, const std::string&, const std::string&); enum class editResult { error, changes, nochanges }; - editResult editFile (Task&); + editResult editFile(Task&); static const std::string ANNOTATION_EDIT_MARKER; }; diff --git a/src/commands/CmdExec.cpp b/src/commands/CmdExec.cpp index 111c77798..c3457cf67 100644 --- a/src/commands/CmdExec.cpp +++ b/src/commands/CmdExec.cpp @@ -25,39 +25,38 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdExec::CmdExec () -{ - _keyword = "execute"; - _usage = "task execute "; - _description = "Executes external commands and scripts"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdExec::CmdExec() { + _keyword = "execute"; + _usage = "task execute "; + _description = "Executes external commands and scripts"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdExec::execute (std::string&) -{ - std::string command = join (" ", Context::getContext ().cli2.getWords ()); +int CmdExec::execute(std::string&) { + std::string command = join(" ", Context::getContext().cli2.getWords()); - if (command.empty()) - { - Context::getContext ().error ("Cannot execute an empty command."); + if (command.empty()) { + Context::getContext().error("Cannot execute an empty command."); return 1; - } - else - return system (command.c_str ()); + } else + return system(command.c_str()); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdExec.h b/src/commands/CmdExec.h index 82e0d16ec..4847ebff7 100644 --- a/src/commands/CmdExec.h +++ b/src/commands/CmdExec.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDEXEC #define INCLUDED_CMDEXEC -#include #include -class CmdExec : public Command -{ -public: - CmdExec (); - int execute (std::string&); +#include + +class CmdExec : public Command { + public: + CmdExec(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdExport.cpp b/src/commands/CmdExport.cpp index f5c43f128..2924645dd 100644 --- a/src/commands/CmdExport.cpp +++ b/src/commands/CmdExport.cpp @@ -25,104 +25,90 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include #include +#include #include -#include -#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdExport::CmdExport () -{ - _keyword = "export"; - _usage = "task export []"; - _description = "Exports tasks in JSON format"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdExport::CmdExport() { + _keyword = "export"; + _usage = "task export []"; + _description = "Exports tasks in JSON format"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::migration; + _category = Command::Category::migration; } //////////////////////////////////////////////////////////////////////////////// -int CmdExport::execute (std::string& output) -{ +int CmdExport::execute(std::string& output) { int rc = 0; - auto words = Context::getContext ().cli2.getWords (); + auto words = Context::getContext().cli2.getWords(); std::string selectedReport = ""; - if (words.size () == 1) - { + if (words.size() == 1) { // Find the report matching the prompt - for (auto& command : Context::getContext ().commands) - { - if (command.second->category () == Command::Category::report && - closeEnough(command.second->keyword (), words[0])) - { - selectedReport = command.second->keyword (); + for (auto& command : Context::getContext().commands) { + if (command.second->category() == Command::Category::report && + closeEnough(command.second->keyword(), words[0])) { + selectedReport = command.second->keyword(); break; } } - if (selectedReport.empty ()) { - throw format("Unable to find report that matches '{1}'.", words[0]); + if (selectedReport.empty()) { + throw format("Unable to find report that matches '{1}'.", words[0]); } } - auto reportSort = Context::getContext ().config.get ("report." + selectedReport + ".sort"); - auto reportFilter = Context::getContext ().config.get ("report." + selectedReport + ".filter"); + auto reportSort = Context::getContext().config.get("report." + selectedReport + ".sort"); + auto reportFilter = Context::getContext().config.get("report." + selectedReport + ".filter"); - auto sortOrder = split (reportSort, ','); - if (sortOrder.size () != 0 && - sortOrder[0] != "none") { - validateSortColumns (sortOrder); - } + auto sortOrder = split(reportSort, ','); + if (sortOrder.size() != 0 && sortOrder[0] != "none") { + validateSortColumns(sortOrder); + } // Add the report filter to any existing filter. - if (reportFilter != "") - Context::getContext ().cli2.addFilter (reportFilter); - - // Make sure reccurent tasks are generated. - handleUntil (); - handleRecurrence (); + if (reportFilter != "") Context::getContext().cli2.addFilter(reportFilter); // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - std::vector sequence; - if (sortOrder.size () && - sortOrder[0] == "none") - { + std::vector sequence; + if (sortOrder.size() && sortOrder[0] == "none") { // Assemble a sequence vector that represents the tasks listed in // Context::getContext ().cli2._uuid_ranges, in the order in which they appear. This // equates to no sorting, just a specified order. - sortOrder.clear (); - for (auto& i : Context::getContext ().cli2._uuid_list) - for (unsigned int t = 0; t < filtered.size (); ++t) - if (filtered[t].get ("uuid") == i) - sequence.push_back (t); - } - else - { + sortOrder.clear(); + for (auto& i : Context::getContext().cli2._uuid_list) + for (unsigned int t = 0; t < filtered.size(); ++t) + if (filtered[t].get("uuid") == i) sequence.push_back(t); + } else { // sort_tasks requires the order array initially be identity - for (unsigned int i = 0; i < filtered.size (); ++i) - sequence.push_back (i); + for (unsigned int i = 0; i < filtered.size(); ++i) sequence.push_back(i); // if no sort order, sort by id - if (!sortOrder.size ()) { - reportSort = "id"; + if (!sortOrder.size()) { + reportSort = "id,uuid"; } // Sort the tasks. - sort_tasks (filtered, sequence, reportSort); + sort_tasks(filtered, sequence, reportSort); } // Export == render. @@ -131,48 +117,39 @@ int CmdExport::execute (std::string& output) // Obey 'limit:N'. int rows = 0; int lines = 0; - Context::getContext ().getLimits (rows, lines); + Context::getContext().getLimits(rows, lines); int limit = (rows > lines ? rows : lines); // Is output contained within a JSON array? - bool json_array = Context::getContext ().config.getBoolean ("json.array"); + bool json_array = Context::getContext().config.getBoolean("json.array"); // Compose output. - if (json_array) - output += "[\n"; + if (json_array) output += "[\n"; int counter = 0; - for (auto& t : sequence) - { + for (auto& t : sequence) { auto task = filtered[t]; - if (counter) - { - if (json_array) - output += ','; + if (counter) { + if (json_array) output += ','; output += '\n'; } - output += task.composeJSON (true); + output += task.composeJSON(true); ++counter; - if (limit && counter >= limit) - break; + if (limit && counter >= limit) break; } - if (filtered.size ()) - output += '\n'; + if (filtered.size()) output += '\n'; - if (json_array) - output += "]\n"; + if (json_array) output += "]\n"; - Context::getContext ().time_render_us += timer.total_us (); + Context::getContext().time_render_us += timer.total_us(); return rc; } //////////////////////////////////////////////////////////////////////////////// -void CmdExport::validateSortColumns (std::vector & columns) -{ - for (auto& col : columns) - legacySortColumnMap (col); +void CmdExport::validateSortColumns(std::vector& columns) { + for (auto& col : columns) legacySortColumnMap(col); } diff --git a/src/commands/CmdExport.h b/src/commands/CmdExport.h index 24eb252c3..db3faee90 100644 --- a/src/commands/CmdExport.h +++ b/src/commands/CmdExport.h @@ -27,16 +27,17 @@ #ifndef INCLUDED_CMDEXPORT #define INCLUDED_CMDEXPORT -#include #include -class CmdExport : public Command -{ -public: - CmdExport (); - int execute (std::string&); -private: - void validateSortColumns (std::vector &); +#include + +class CmdExport : public Command { + public: + CmdExport(); + int execute(std::string&); + + private: + void validateSortColumns(std::vector&); }; #endif diff --git a/src/commands/CmdGet.cpp b/src/commands/CmdGet.cpp index 38aae7c35..ec7c8da0a 100644 --- a/src/commands/CmdGet.cpp +++ b/src/commands/CmdGet.cpp @@ -25,28 +25,29 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include -#include +#include #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdGet::CmdGet () -{ - _keyword = "_get"; - _usage = "task _get [ ...]"; - _description = "DOM Accessor"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdGet::CmdGet() { + _keyword = "_get"; + _usage = "task _get [ ...]"; + _description = "DOM Accessor"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// @@ -55,39 +56,32 @@ CmdGet::CmdGet () // // It is an error to specify no DOM references. // It is not an error for a DOM reference to resolve to a blank value. -int CmdGet::execute (std::string& output) -{ - std::vector results; - for (auto& arg : Context::getContext ().cli2._args) - { - switch (arg._lextype) - { - case Lexer::Type::dom: - { +int CmdGet::execute(std::string& output) { + std::vector results; + for (auto& arg : Context::getContext().cli2._args) { + switch (arg._lextype) { + case Lexer::Type::dom: { Variant result; - if (getDOM (arg.attribute ("raw"), NULL, result)) - results.emplace_back (result); + if (getDOM(arg.attribute("raw"), NULL, result)) + results.emplace_back(result); else - results.emplace_back (""); - } - break; + results.emplace_back(""); + } break; - // Look for non-refs to complain about. - case Lexer::Type::word: - case Lexer::Type::identifier: - if (! arg.hasTag ("BINARY") && - ! arg.hasTag ("CMD")) - throw format ("'{1}' is not a DOM reference.", arg.attribute ("raw")); + // Look for non-refs to complain about. + case Lexer::Type::word: + case Lexer::Type::identifier: + if (!arg.hasTag("BINARY") && !arg.hasTag("CMD")) + throw format("'{1}' is not a DOM reference.", arg.attribute("raw")); - default: - break; + default: + break; } } - if (results.size () == 0) - throw std::string ("No DOM reference specified."); + if (results.size() == 0) throw std::string("No DOM reference specified."); - output = join (" ", results); + output = join(" ", results); output += '\n'; return 0; } diff --git a/src/commands/CmdGet.h b/src/commands/CmdGet.h index 68fe81f84..18f651a0c 100644 --- a/src/commands/CmdGet.h +++ b/src/commands/CmdGet.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDGET #define INCLUDED_CMDGET -#include #include -class CmdGet : public Command -{ -public: - CmdGet (); - int execute (std::string&); +#include + +class CmdGet : public Command { + public: + CmdGet(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdHelp.cpp b/src/commands/CmdHelp.cpp index f755735a7..c66715bff 100644 --- a/src/commands/CmdHelp.cpp +++ b/src/commands/CmdHelp.cpp @@ -25,218 +25,209 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include -#include +#include #include +#include #include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdHelp::CmdHelp () -{ - _keyword = "help"; - _usage = "task help ['usage']"; - _description = "Displays this usage help text"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdHelp::CmdHelp() { + _keyword = "help"; + _usage = "task help ['usage']"; + _description = "Displays this usage help text"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdHelp::execute (std::string& output) -{ - auto words = Context::getContext ().cli2.getWords (); - if (words.size () == 1 && closeEnough ("usage", words[0])) - output = '\n' - + composeUsage () - + '\n'; +int CmdHelp::execute(std::string& output) { + auto words = Context::getContext().cli2.getWords(); + if (words.size() == 1 && closeEnough("usage", words[0])) + output = '\n' + composeUsage() + '\n'; else - output = '\n' - + composeUsage () - + '\n' - + "Documentation for Taskwarrior can be found using 'man task', 'man taskrc', 'man " - "task-color', 'man task-sync' or at https://taskwarrior.org\n" - "\n" - "The general form of commands is:\n" - " task [] []\n" - "\n" - "The consists of zero or more restrictions on which tasks to select, " - "such as:\n" - " task \n" - " task 28 \n" - " task +weekend \n" - " task project:Home due.before:today \n" - " task ebeeab00-ccf8-464b-8b58-f7f2d606edfb \n" - "\n" - "By default, filter elements are combined with an implicit 'and' operator, but " - "'or' and 'xor' may also be used, provided parentheses are included:\n" - " task '(/[Cc]at|[Dd]og/ or /[0-9]+/)' \n" - "\n" - "A filter may target specific tasks using ID or UUID numbers. To specify " - "multiple tasks use one of these forms:\n" - " task 1,2,3 delete\n" - " task 1-3 info\n" - " task 1,2-5,19 modify pri:H\n" - " task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info\n" - "\n" - "The consist of zero or more changes to apply to the selected tasks, " - "such as:\n" - " task project:Home\n" - " task +weekend +garden due:tomorrow\n" - " task Description/annotation text\n" - " task /from/to/ <- replace first match\n" - " task /from/to/g <- replace all matches\n" - "\n" - "Tags are arbitrary words, any quantity:\n" - " +tag The + means add the tag\n" - " -tag The - means remove the tag\n" - "\n" - "Built-in attributes are:\n" - " description: Task description text\n" - " status: Status of task - pending, completed, deleted, waiting\n" - " project: Project name\n" - " priority: Priority\n" - " due: Due date\n" - " recur: Recurrence frequency\n" - " until: Expiration date of a task\n" - " limit: Desired number of rows in report, or 'page'\n" - " wait: Date until task becomes pending\n" - " entry: Date task was created\n" - " end: Date task was completed/deleted\n" - " start: Date task was started\n" - " scheduled: Date task is scheduled to start\n" - " modified: Date task was last modified\n" - " depends: Other tasks that this task depends upon\n" - "\n" - "Attribute modifiers make filters more precise. Supported modifiers are:\n" - "\n" - " Modifiers Example Equivalent Meaning\n" - " ---------------- ----------------- ------------------- -------------------------\n" - " due:today due = today Fuzzy match\n" - " not due.not:today due != today Fuzzy non-match\n" - " before, below due.before:today due < today Exact date comparison\n" - " after, above due.after:today due >= tomorrow Exact date comparison\n" - " none project.none: project == '' Empty\n" - " any project.any: project !== '' Not empty\n" - " is, equals project.is:x project == x Exact match\n" - " isnt project.isnt:x project !== x Exact non-match\n" - " has, contains desc.has:Hello desc ~ Hello Pattern match\n" - " hasnt, desc.hasnt:Hello desc !~ Hello Pattern non-match\n" - " startswith, left desc.left:Hel desc ~ '^Hel' Beginning match\n" - " endswith, right desc.right:llo desc ~ 'llo$' End match\n" - " word desc.word:Hello desc ~ '\\bHello\\b' Boundaried word match\n" - " noword desc.noword:Hello desc !~ '\\bHello\\b' Boundaried word non-match\n" - "\n" - "Alternately algebraic expressions support:\n" - " and or xor Logical operators\n" - " < <= = != >= > Relational operators\n" - " ( ) Precedence\n" - "\n" - " task due.before:eom priority.not:L list\n" - " task '(due < eom and priority != L)' list\n" - "\n" - "The default .taskrc file can be overridden with:\n" - " task ... rc: ...\n" - " task ... rc:~/.alt_taskrc ...\n" - "\n" - "The values in .taskrc (or alternate) can be overridden with:\n" - " task ... rc.= ...\n" - " task rc.color=off list\n" - "\n" - "Any command or attribute name may be abbreviated if still unique:\n" - " task list project:Home\n" - " task li pro:Home\n" - "\n" - "Some task descriptions need to be escaped because of the shell:\n" - " task add \"quoted ' quote\"\n" - " task add escaped \\' quote\n" - "\n" - "The argument -- tells Taskwarrior to treat all other args as description, even " - "if they would otherwise be attributes or tags:\n" - " task add -- project:Home needs scheduling\n" - "\n" - "Many characters have special meaning to the shell, including:\n" - " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~\n" - "\n"; + output = + '\n' + composeUsage() + '\n' + + "Documentation for Taskwarrior can be found using 'man task', 'man taskrc', 'man " + "task-color', 'man task-sync' or at https://taskwarrior.org\n" + "\n" + "The general form of commands is:\n" + " task [] []\n" + "\n" + "The consists of zero or more restrictions on which tasks to select, " + "such as:\n" + " task \n" + " task 28 \n" + " task +weekend \n" + " task project:Home due.before:today \n" + " task ebeeab00-ccf8-464b-8b58-f7f2d606edfb \n" + "\n" + "By default, filter elements are combined with an implicit 'and' operator, but " + "'or' and 'xor' may also be used, provided parentheses are included:\n" + " task '(/[Cc]at|[Dd]og/ or /[0-9]+/)' \n" + "\n" + "A filter may target specific tasks using ID or UUID numbers. To specify " + "multiple tasks use one of these forms:\n" + " task 1,2,3 delete\n" + " task 1-3 info\n" + " task 1,2-5,19 modify pri:H\n" + " task 4-7 ebeeab00-ccf8-464b-8b58-f7f2d606edfb info\n" + "\n" + "The consist of zero or more changes to apply to the selected tasks, " + "such as:\n" + " task project:Home\n" + " task +weekend +garden due:tomorrow\n" + " task Description/annotation text\n" + " task /from/to/ <- replace first match\n" + " task /from/to/g <- replace all matches\n" + "\n" + "Tags are arbitrary words, any quantity:\n" + " +tag The + means add the tag\n" + " -tag The - means remove the tag\n" + "\n" + "Built-in attributes are:\n" + " description: Task description text\n" + " status: Status of task - pending, completed, deleted, waiting\n" + " project: Project name\n" + " priority: Priority\n" + " due: Due date\n" + " recur: Recurrence frequency\n" + " until: Expiration date of a task\n" + " limit: Desired number of rows in report, or 'page'\n" + " wait: Date until task becomes pending\n" + " entry: Date task was created\n" + " end: Date task was completed/deleted\n" + " start: Date task was started\n" + " scheduled: Date task is scheduled to start\n" + " modified: Date task was last modified\n" + " depends: Other tasks that this task depends upon\n" + "\n" + "Attribute modifiers make filters more precise. Supported modifiers are:\n" + "\n" + " Modifiers Example Equivalent Meaning\n" + " ---------------- ----------------- ------------------- -------------------------\n" + " due:today due = today Fuzzy match\n" + " not due.not:today due != today Fuzzy non-match\n" + " before, below due.before:today due < today Exact date comparison\n" + " after, above due.after:today due >= tomorrow Exact date comparison\n" + " none project.none: project == '' Empty\n" + " any project.any: project !== '' Not empty\n" + " is, equals project.is:x project == x Exact match\n" + " isnt project.isnt:x project !== x Exact non-match\n" + " has, contains desc.has:Hello desc ~ Hello Pattern match\n" + " hasnt, desc.hasnt:Hello desc !~ Hello Pattern non-match\n" + " startswith, left desc.left:Hel desc ~ '^Hel' Beginning match\n" + " endswith, right desc.right:llo desc ~ 'llo$' End match\n" + " word desc.word:Hello desc ~ '\\bHello\\b' Boundaried word match\n" + " noword desc.noword:Hello desc !~ '\\bHello\\b' Boundaried word non-match\n" + "\n" + "Alternately algebraic expressions support:\n" + " and or xor Logical operators\n" + " < <= = != >= > Relational operators\n" + " ( ) Precedence\n" + "\n" + " task due.before:eom priority.not:L list\n" + " task '(due < eom and priority != L)' list\n" + "\n" + "The default .taskrc file can be overridden with:\n" + " task ... rc: ...\n" + " task ... rc:~/.alt_taskrc ...\n" + "\n" + "The values in .taskrc (or alternate) can be overridden with:\n" + " task ... rc.= ...\n" + " task rc.color=off list\n" + "\n" + "Any command or attribute name may be abbreviated if still unique:\n" + " task list project:Home\n" + " task li pro:Home\n" + "\n" + "Some task descriptions need to be escaped because of the shell:\n" + " task add \"quoted ' quote\"\n" + " task add escaped \\' quote\n" + "\n" + "The argument -- tells Taskwarrior to treat all other args as description, even " + "if they would otherwise be attributes or tags:\n" + " task add -- project:Home needs scheduling\n" + "\n" + "Many characters have special meaning to the shell, including:\n" + " $ ! ' \" ( ) ; \\ ` * ? { } [ ] < > | & % # ~\n" + "\n"; - /* - TODO To be included later, before the 'precedence' line. + /* + TODO To be included later, before the 'precedence' line. - " + - Addition, Subtraktion\n" \ - " ! Negation\n" \ - " ~ !~ Treffer, kein Treffer\n" \ - */ + " + - Addition, Subtraktion\n" \ + " ! Negation\n" \ + " ~ !~ Treffer, kein Treffer\n" \ + */ return 0; } //////////////////////////////////////////////////////////////////////////////// -std::string CmdHelp::composeUsage () const -{ +std::string CmdHelp::composeUsage() const { Table view; - view.width (Context::getContext ().getWidth ()); - view.add (""); - view.add (""); - view.add (""); + view.width(Context::getContext().getWidth()); + view.add(""); + view.add(""); + view.add(""); // Static first row. - auto row = view.addRow (); - view.set (row, 0, "Usage:"); - view.set (row, 1, "task"); - view.set (row, 2, "Runs rc.default.command, if specified."); + auto row = view.addRow(); + view.set(row, 0, "Usage:"); + view.set(row, 1, "task"); + view.set(row, 2, "Runs rc.default.command, if specified."); // Obsolete method of getting a list of all commands. - std::vector all; - for (auto& cmd : Context::getContext ().commands) - all.push_back (cmd.first); + std::vector all; + for (auto& cmd : Context::getContext().commands) all.push_back(cmd.first); // Sort alphabetically by usage. - std::sort (all.begin (), all.end ()); + std::sort(all.begin(), all.end()); // Add the regular commands. - for (auto& name : all) - { - if (name[0] != '_') - { - row = view.addRow (); - view.set (row, 1, Context::getContext ().commands[name]->usage ()); - view.set (row, 2, Context::getContext ().commands[name]->description ()); + for (auto& name : all) { + if (name[0] != '_') { + row = view.addRow(); + view.set(row, 1, Context::getContext().commands[name]->usage()); + view.set(row, 2, Context::getContext().commands[name]->description()); } } // Add the helper commands. - for (auto& name : all) - { - if (name[0] == '_') - { - row = view.addRow (); - view.set (row, 1, Context::getContext ().commands[name]->usage ()); - view.set (row, 2, Context::getContext ().commands[name]->description ()); + for (auto& name : all) { + if (name[0] == '_') { + row = view.addRow(); + view.set(row, 1, Context::getContext().commands[name]->usage()); + view.set(row, 2, Context::getContext().commands[name]->description()); } } // Add the aliases commands. - row = view.addRow (); - view.set (row, 1, " "); + row = view.addRow(); + view.set(row, 1, " "); - for (auto& alias : Context::getContext ().config) - { - if (alias.first.substr (0, 6) == "alias.") - { - row = view.addRow (); - view.set (row, 1, alias.first.substr (6)); - view.set (row, 2, format ("Aliased to '{1}'", alias.second)); + for (auto& alias : Context::getContext().config) { + if (alias.first.substr(0, 6) == "alias.") { + row = view.addRow(); + view.set(row, 1, alias.first.substr(6)); + view.set(row, 2, format("Aliased to '{1}'", alias.second)); } } - return view.render (); + return view.render(); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdHelp.h b/src/commands/CmdHelp.h index 7b793f6a6..af7e2a84a 100644 --- a/src/commands/CmdHelp.h +++ b/src/commands/CmdHelp.h @@ -27,17 +27,17 @@ #ifndef INCLUDED_CMDHELP #define INCLUDED_CMDHELP -#include #include -class CmdHelp : public Command -{ -public: - CmdHelp (); - int execute (std::string&); +#include -private: - std::string composeUsage () const; +class CmdHelp : public Command { + public: + CmdHelp(); + int execute(std::string&); + + private: + std::string composeUsage() const; }; #endif diff --git a/src/commands/CmdHistory.cpp b/src/commands/CmdHistory.cpp index 8e6fa7076..d379a89dd 100644 --- a/src/commands/CmdHistory.cpp +++ b/src/commands/CmdHistory.cpp @@ -25,352 +25,300 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include +#include #include #include -#include #include #include -#include -#define STRING_CMD_HISTORY_YEAR "Year" -#define STRING_CMD_HISTORY_MONTH "Month" -#define STRING_CMD_HISTORY_DAY "Day" -#define STRING_CMD_HISTORY_ADDED "Added" -#define STRING_CMD_HISTORY_COMP "Completed" -#define STRING_CMD_HISTORY_DEL "Deleted" +#include + +#define STRING_CMD_HISTORY_YEAR "Year" +#define STRING_CMD_HISTORY_MONTH "Month" +#define STRING_CMD_HISTORY_DAY "Day" +#define STRING_CMD_HISTORY_ADDED "Added" +#define STRING_CMD_HISTORY_COMP "Completed" +#define STRING_CMD_HISTORY_DEL "Deleted" //////////////////////////////////////////////////////////////////////////////// -template -CmdHistoryBase::CmdHistoryBase () -{ - _keyword = HistoryStrategy::keyword; - _usage = HistoryStrategy::usage; - _description = HistoryStrategy::description; +template +CmdHistoryBase::CmdHistoryBase() { + _keyword = HistoryStrategy::keyword; + _usage = HistoryStrategy::usage; + _description = HistoryStrategy::description; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// -template -void CmdHistoryBase::outputGraphical (std::string& output) -{ - auto widthOfBar = Context::getContext ().getWidth () - HistoryStrategy::labelWidth; +template +void CmdHistoryBase::outputGraphical(std::string& output) { + auto widthOfBar = Context::getContext().getWidth() - HistoryStrategy::labelWidth; // Now build the view. Table view; - setHeaderUnderline (view); - view.width (Context::getContext ().getWidth ()); + setHeaderUnderline(view); + view.width(Context::getContext().getWidth()); - HistoryStrategy::setupTableDates (view); + HistoryStrategy::setupTableDates(view); - view.add ("Number Added/Completed/Deleted", true, false); // Fixed. + view.add("Number Added/Completed/Deleted", true, false); // Fixed. - Color color_add (Context::getContext ().config.get ("color.history.add")); - Color color_done (Context::getContext ().config.get ("color.history.done")); - Color color_delete (Context::getContext ().config.get ("color.history.delete")); - Color label (Context::getContext ().config.get ("color.label")); + Color color_add(Context::getContext().config.get("color.history.add")); + Color color_done(Context::getContext().config.get("color.history.done")); + Color color_delete(Context::getContext().config.get("color.history.delete")); + Color label(Context::getContext().config.get("color.label")); // Determine the longest line, and the longest "added" line. auto maxAddedLine = 0; auto maxRemovedLine = 0; - for (auto& i : groups) - { + for (auto& i : groups) { if (completedGroup[i.first] + deletedGroup[i.first] > maxRemovedLine) maxRemovedLine = completedGroup[i.first] + deletedGroup[i.first]; - if (addedGroup[i.first] > maxAddedLine) - maxAddedLine = addedGroup[i.first]; + if (addedGroup[i.first] > maxAddedLine) maxAddedLine = addedGroup[i.first]; } auto maxLine = maxAddedLine + maxRemovedLine; - if (maxLine > 0) - { + if (maxLine > 0) { unsigned int leftOffset = (widthOfBar * maxAddedLine) / maxLine; - auto totalAdded = 0; - auto totalCompleted = 0; - auto totalDeleted = 0; - time_t priorTime = 0; auto row = 0; - for (auto& i : groups) - { - row = view.addRow (); + for (auto& i : groups) { + row = view.addRow(); - totalAdded += addedGroup[i.first]; - totalCompleted += completedGroup[i.first]; - totalDeleted += deletedGroup[i.first]; - - HistoryStrategy::insertRowDate (view, row, i.first, priorTime); + HistoryStrategy::insertRowDate(view, row, i.first, priorTime); priorTime = i.first; - unsigned int addedBar = (widthOfBar * addedGroup[i.first]) / maxLine; + unsigned int addedBar = (widthOfBar * addedGroup[i.first]) / maxLine; unsigned int completedBar = (widthOfBar * completedGroup[i.first]) / maxLine; - unsigned int deletedBar = (widthOfBar * deletedGroup[i.first]) / maxLine; + unsigned int deletedBar = (widthOfBar * deletedGroup[i.first]) / maxLine; std::string bar; - if (Context::getContext ().color ()) - { + if (Context::getContext().color()) { std::string aBar; - if (addedGroup[i.first]) - { - aBar = format (addedGroup[i.first]); - while (aBar.length () < addedBar) - aBar = ' ' + aBar; + if (addedGroup[i.first]) { + aBar = format(addedGroup[i.first]); + while (aBar.length() < addedBar) aBar = ' ' + aBar; } std::string cBar; - if (completedGroup[i.first]) - { - cBar = format (completedGroup[i.first]); - while (cBar.length () < completedBar) - cBar = ' ' + cBar; + if (completedGroup[i.first]) { + cBar = format(completedGroup[i.first]); + while (cBar.length() < completedBar) cBar = ' ' + cBar; } std::string dBar; - if (deletedGroup[i.first]) - { - dBar = format (deletedGroup[i.first]); - while (dBar.length () < deletedBar) - dBar = ' ' + dBar; + if (deletedGroup[i.first]) { + dBar = format(deletedGroup[i.first]); + while (dBar.length() < deletedBar) dBar = ' ' + dBar; } - bar += std::string (leftOffset - aBar.length (), ' '); - bar += color_add.colorize (aBar); - bar += color_done.colorize (cBar); - bar += color_delete.colorize (dBar); - } - else - { - std::string aBar; while (aBar.length () < addedBar) aBar += '+'; - std::string cBar; while (cBar.length () < completedBar) cBar += 'X'; - std::string dBar; while (dBar.length () < deletedBar) dBar += '-'; + bar += std::string(leftOffset - aBar.length(), ' '); + bar += color_add.colorize(aBar); + bar += color_done.colorize(cBar); + bar += color_delete.colorize(dBar); + } else { + std::string aBar; + while (aBar.length() < addedBar) aBar += '+'; + std::string cBar; + while (cBar.length() < completedBar) cBar += 'X'; + std::string dBar; + while (dBar.length() < deletedBar) dBar += '-'; - bar += std::string (leftOffset - aBar.length (), ' '); + bar += std::string(leftOffset - aBar.length(), ' '); bar += aBar + cBar + dBar; } - view.set (row, HistoryStrategy::dateFieldCount + 0, bar); + view.set(row, HistoryStrategy::dateFieldCount + 0, bar); } } std::stringstream out; - if (view.rows ()) - { - out << optionalBlankLine () - << view.render () - << '\n'; + if (view.rows()) { + out << optionalBlankLine() << view.render() << '\n'; - if (Context::getContext ().color ()) - out << format ("Legend: {1}, {2}, {3}", - color_add.colorize (STRING_CMD_HISTORY_ADDED), - color_done.colorize (STRING_CMD_HISTORY_COMP), - color_delete.colorize (STRING_CMD_HISTORY_DEL)) - << optionalBlankLine () - << '\n'; + if (Context::getContext().color()) + out << format("Legend: {1}, {2}, {3}", color_add.colorize(STRING_CMD_HISTORY_ADDED), + color_done.colorize(STRING_CMD_HISTORY_COMP), + color_delete.colorize(STRING_CMD_HISTORY_DEL)) + << optionalBlankLine() << '\n'; else out << "Legend: + Added, X Completed, - Deleted\n"; - } - else - { - Context::getContext ().footnote ("No tasks."); + } else { + Context::getContext().footnote("No tasks."); rc = 1; } - output = out.str (); + output = out.str(); } //////////////////////////////////////////////////////////////////////////////// -template -void CmdHistoryBase::outputTabular (std::string& output) -{ +template +void CmdHistoryBase::outputTabular(std::string& output) { Table view; - setHeaderUnderline (view); - view.width (Context::getContext ().getWidth ()); + setHeaderUnderline(view); + view.width(Context::getContext().getWidth()); - HistoryStrategy::setupTableDates (view); + HistoryStrategy::setupTableDates(view); - view.add (STRING_CMD_HISTORY_ADDED, false); - view.add (STRING_CMD_HISTORY_COMP, false); - view.add (STRING_CMD_HISTORY_DEL, false); - view.add ("Net", false); + view.add(STRING_CMD_HISTORY_ADDED, false); + view.add(STRING_CMD_HISTORY_COMP, false); + view.add(STRING_CMD_HISTORY_DEL, false); + view.add("Net", false); - auto totalAdded = 0; + auto totalAdded = 0; auto totalCompleted = 0; - auto totalDeleted = 0; + auto totalDeleted = 0; auto row = 0; time_t lastTime = 0; - for (auto& i : groups) - { - row = view.addRow (); + for (auto& i : groups) { + row = view.addRow(); - totalAdded += addedGroup [i.first]; - totalCompleted += completedGroup [i.first]; - totalDeleted += deletedGroup [i.first]; + totalAdded += addedGroup[i.first]; + totalCompleted += completedGroup[i.first]; + totalDeleted += deletedGroup[i.first]; - HistoryStrategy::insertRowDate (view, row, i.first, lastTime); + HistoryStrategy::insertRowDate(view, row, i.first, lastTime); lastTime = i.first; auto net = 0; - if (addedGroup.find (i.first) != addedGroup.end ()) - { - view.set (row, HistoryStrategy::dateFieldCount + 0, addedGroup[i.first]); - net +=addedGroup[i.first]; + if (addedGroup.find(i.first) != addedGroup.end()) { + view.set(row, HistoryStrategy::dateFieldCount + 0, addedGroup[i.first]); + net += addedGroup[i.first]; } - if (completedGroup.find (i.first) != completedGroup.end ()) - { - view.set (row, HistoryStrategy::dateFieldCount + 1, completedGroup[i.first]); + if (completedGroup.find(i.first) != completedGroup.end()) { + view.set(row, HistoryStrategy::dateFieldCount + 1, completedGroup[i.first]); net -= completedGroup[i.first]; } - if (deletedGroup.find (i.first) != deletedGroup.end ()) - { - view.set (row, HistoryStrategy::dateFieldCount + 2, deletedGroup[i.first]); + if (deletedGroup.find(i.first) != deletedGroup.end()) { + view.set(row, HistoryStrategy::dateFieldCount + 2, deletedGroup[i.first]); net -= deletedGroup[i.first]; } Color net_color; - if (Context::getContext ().color () && net) - net_color = net > 0 - ? Color (Color::red) - : Color (Color::green); + if (Context::getContext().color() && net) + net_color = net > 0 ? Color(Color::red) : Color(Color::green); - view.set (row, HistoryStrategy::dateFieldCount + 3, net, net_color); + view.set(row, HistoryStrategy::dateFieldCount + 3, net, net_color); } - if (view.rows ()) - { + if (view.rows()) { + row = view.addRow(); + view.set(row, 1, " "); row = view.addRow(); - view.set (row, 1, " "); - row = view.addRow (); Color row_color; - if (Context::getContext ().color ()) - row_color = Color (Color::nocolor, Color::nocolor, false, true, false); + if (Context::getContext().color()) + row_color = Color(Color::nocolor, Color::nocolor, false, true, false); - view.set (row, HistoryStrategy::dateFieldCount - 1, "Average", row_color); - view.set (row, HistoryStrategy::dateFieldCount + 0, totalAdded / (view.rows () - 2), row_color); - view.set (row, HistoryStrategy::dateFieldCount + 1, totalCompleted / (view.rows () - 2), row_color); - view.set (row, HistoryStrategy::dateFieldCount + 2, totalDeleted / (view.rows () - 2), row_color); - view.set (row, HistoryStrategy::dateFieldCount + 3, (totalAdded - totalCompleted - totalDeleted) / (view.rows () - 2), row_color); + view.set(row, HistoryStrategy::dateFieldCount - 1, "Average", row_color); + view.set(row, HistoryStrategy::dateFieldCount + 0, totalAdded / (view.rows() - 2), row_color); + view.set(row, HistoryStrategy::dateFieldCount + 1, totalCompleted / (view.rows() - 2), + row_color); + view.set(row, HistoryStrategy::dateFieldCount + 2, totalDeleted / (view.rows() - 2), row_color); + view.set(row, HistoryStrategy::dateFieldCount + 3, + (totalAdded - totalCompleted - totalDeleted) / (view.rows() - 2), row_color); } std::stringstream out; - if (view.rows ()) - out << optionalBlankLine () - << view.render () - << '\n'; - else - { - Context::getContext ().footnote ("No tasks."); + if (view.rows()) + out << optionalBlankLine() << view.render() << '\n'; + else { + Context::getContext().footnote("No tasks."); rc = 1; } - output = out.str (); + output = out.str(); } ////////////////////////////////////////////////////////////////////////////i -class MonthlyHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfMonth (); +class MonthlyHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfMonth(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); - if (y != last_y) - view.set (row, 0, y); + if (y != last_y) view.set(row, 0, y); - view.set (row, 1, Datetime::monthName (m)); + view.set(row, 1, Datetime::monthName(m)); } - static constexpr const char* keyword = "history.monthly"; - static constexpr const char* usage = "task history.monthly"; - static constexpr const char* description = "Shows a report of task history, by month"; + static constexpr const char* keyword = "history.monthly"; + static constexpr const char* usage = "task history.monthly"; + static constexpr const char* description = "Shows a report of task history, by month"; static constexpr unsigned int dateFieldCount = 2; - static constexpr bool graphical = false; - static constexpr unsigned int labelWidth = 0; // unused. + static constexpr bool graphical = false; + static constexpr unsigned int labelWidth = 0; // unused. }; //////////////////////////////////////////////////////////////////////////////// -template -int CmdHistoryBase::execute (std::string& output) -{ +template +int CmdHistoryBase::execute(std::string& output) { rc = 0; // TODO is this necessary? - groups.clear (); - addedGroup.clear (); - deletedGroup.clear (); - completedGroup.clear (); + groups.clear(); + addedGroup.clear(); + deletedGroup.clear(); + completedGroup.clear(); // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - for (auto& task : filtered) - { - Datetime entry (task.get_date ("entry")); + for (auto& task : filtered) { + Datetime entry(task.get_date("entry")); Datetime end; - if (task.has ("end")) - end = Datetime (task.get_date ("end")); + if (task.has("end")) end = Datetime(task.get_date("end")); - auto epoch = HistoryStrategy::getRelevantDate (entry).toEpoch (); + auto epoch = HistoryStrategy::getRelevantDate(entry).toEpoch(); groups[epoch] = 0; // Every task has an entry date, but exclude templates. - if (task.getStatus () != Task::recurring) - ++addedGroup[epoch]; + if (task.getStatus() != Task::recurring) ++addedGroup[epoch]; // All deleted tasks have an end date. - if (task.getStatus () == Task::deleted) - { - epoch = HistoryStrategy::getRelevantDate (end).toEpoch (); + if (task.getStatus() == Task::deleted) { + epoch = HistoryStrategy::getRelevantDate(end).toEpoch(); groups[epoch] = 0; ++deletedGroup[epoch]; } // All completed tasks have an end date. - else if (task.getStatus () == Task::completed) - { - epoch = HistoryStrategy::getRelevantDate (end).toEpoch (); + else if (task.getStatus() == Task::completed) { + epoch = HistoryStrategy::getRelevantDate(end).toEpoch(); groups[epoch] = 0; ++completedGroup[epoch]; } @@ -378,338 +326,251 @@ int CmdHistoryBase::execute (std::string& output) // Now build the view. if (HistoryStrategy::graphical) - this->outputGraphical (output); + this->outputGraphical(output); else - this->outputTabular (output); + this->outputTabular(output); return rc; } ////////////////////////////////////////////////////////////////////////////i -class MonthlyGHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfMonth (); +class MonthlyGHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfMonth(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); - if (y != last_y) - view.set (row, 0, y); + if (y != last_y) view.set(row, 0, y); - view.set (row, 1, Datetime::monthName (m)); + view.set(row, 1, Datetime::monthName(m)); } - static constexpr const char* keyword = "ghistory.monthly"; - static constexpr const char* usage = "task ghistory.monthly"; - static constexpr const char* description = "Shows a graphical report of task history, by month"; + static constexpr const char* keyword = "ghistory.monthly"; + static constexpr const char* usage = "task ghistory.monthly"; + static constexpr const char* description = "Shows a graphical report of task history, by month"; static constexpr unsigned int dateFieldCount = 2; - static constexpr bool graphical = true; - static constexpr unsigned int labelWidth = 15; // length '2017 September ' = 15 + static constexpr bool graphical = true; + static constexpr unsigned int labelWidth = 15; // length '2017 September ' = 15 }; ////////////////////////////////////////////////////////////////////////////i -class AnnualGHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfYear (); - } +class AnnualGHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfYear(); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - } + static void setupTableDates(Table& view) { view.add(STRING_CMD_HISTORY_YEAR, true); } - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); - if (y != last_y) - view.set (row, 0, y); + if (y != last_y) view.set(row, 0, y); } - static constexpr const char* keyword = "ghistory.annual"; - static constexpr const char* usage = "task ghistory.annual"; - static constexpr const char* description = "Shows a graphical report of task history, by year"; + static constexpr const char* keyword = "ghistory.annual"; + static constexpr const char* usage = "task ghistory.annual"; + static constexpr const char* description = "Shows a graphical report of task history, by year"; static constexpr unsigned int dateFieldCount = 1; - static constexpr bool graphical = true; - static constexpr unsigned int labelWidth = 5; // length '2017 ' = 5 + static constexpr bool graphical = true; + static constexpr unsigned int labelWidth = 5; // length '2017 ' = 5 }; ////////////////////////////////////////////////////////////////////////////i -class AnnualHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfYear (); - } +class AnnualHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfYear(); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - } + static void setupTableDates(Table& view) { view.add(STRING_CMD_HISTORY_YEAR, true); } - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); - if (y != last_y) - view.set (row, 0, y); + if (y != last_y) view.set(row, 0, y); } - static constexpr const char* keyword = "history.annual"; - static constexpr const char* usage = "task history.annual"; - static constexpr const char* description = "Shows a report of task history, by year"; + static constexpr const char* keyword = "history.annual"; + static constexpr const char* usage = "task history.annual"; + static constexpr const char* description = "Shows a report of task history, by year"; static constexpr unsigned int dateFieldCount = 1; - static constexpr bool graphical = false; - static constexpr unsigned int labelWidth = 0; // unused. + static constexpr bool graphical = false; + static constexpr unsigned int labelWidth = 0; // unused. }; - ////////////////////////////////////////////////////////////////////////////i -class DailyHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfDay (); +class DailyHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfDay(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); + view.add(STRING_CMD_HISTORY_DAY, false); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - view.add (STRING_CMD_HISTORY_DAY, false); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); bool y_changed = (y != last_y) || (lastTime == 0); bool m_changed = (m != last_m) || (lastTime == 0); - if (y_changed) - view.set (row, 0, y); + if (y_changed) view.set(row, 0, y); - if (y_changed || m_changed) - view.set (row, 1, Datetime::monthName (m)); + if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m)); - view.set (row, 2, d); + view.set(row, 2, d); } - static constexpr const char* keyword = "history.daily"; - static constexpr const char* usage = "task history.daily"; - static constexpr const char* description = "Shows a report of task history, by day"; + static constexpr const char* keyword = "history.daily"; + static constexpr const char* usage = "task history.daily"; + static constexpr const char* description = "Shows a report of task history, by day"; static constexpr unsigned int dateFieldCount = 3; - static constexpr bool graphical = false; - static constexpr unsigned int labelWidth = 0; // unused. + static constexpr bool graphical = false; + static constexpr unsigned int labelWidth = 0; // unused. }; ////////////////////////////////////////////////////////////////////////////i -class DailyGHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfDay (); +class DailyGHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfDay(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); + view.add(STRING_CMD_HISTORY_DAY, false); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - view.add (STRING_CMD_HISTORY_DAY, false); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); bool y_changed = (y != last_y) || (lastTime == 0); bool m_changed = (m != last_m) || (lastTime == 0); - if (y_changed) - view.set (row, 0, y); + if (y_changed) view.set(row, 0, y); - if (y_changed || m_changed) - view.set (row, 1, Datetime::monthName (m)); + if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m)); - view.set (row, 2, d); + view.set(row, 2, d); } - static constexpr const char* keyword = "ghistory.daily"; - static constexpr const char* usage = "task ghistory.daily"; - static constexpr const char* description = "Shows a graphical report of task history, by day"; + static constexpr const char* keyword = "ghistory.daily"; + static constexpr const char* usage = "task ghistory.daily"; + static constexpr const char* description = "Shows a graphical report of task history, by day"; static constexpr unsigned int dateFieldCount = 3; - static constexpr bool graphical = true; - static constexpr unsigned int labelWidth = 19; // length '2017 September Day ' = 19 + static constexpr bool graphical = true; + static constexpr unsigned int labelWidth = 19; // length '2017 September Day ' = 19 }; ////////////////////////////////////////////////////////////////////////////i -class WeeklyHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfWeek (); +class WeeklyHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfWeek(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); + view.add(STRING_CMD_HISTORY_DAY, false); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - view.add (STRING_CMD_HISTORY_DAY, false); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); bool y_changed = (y != last_y) || (lastTime == 0); bool m_changed = (m != last_m) || (lastTime == 0); - if (y_changed) - view.set (row, 0, y); + if (y_changed) view.set(row, 0, y); - if (y_changed || m_changed) - view.set (row, 1, Datetime::monthName (m)); + if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m)); - view.set (row, 2, d); + view.set(row, 2, d); } - static constexpr const char* keyword = "history.weekly"; - static constexpr const char* usage = "task history.weekly"; - static constexpr const char* description = "Shows a report of task history, by week"; + static constexpr const char* keyword = "history.weekly"; + static constexpr const char* usage = "task history.weekly"; + static constexpr const char* description = "Shows a report of task history, by week"; static constexpr unsigned int dateFieldCount = 3; - static constexpr bool graphical = false; - static constexpr unsigned int labelWidth = 0; // unused. + static constexpr bool graphical = false; + static constexpr unsigned int labelWidth = 0; // unused. }; ////////////////////////////////////////////////////////////////////////////i -class WeeklyGHistoryStrategy -{ -public: - static Datetime getRelevantDate (const Datetime& dt) - { - return dt.startOfWeek (); +class WeeklyGHistoryStrategy { + public: + static Datetime getRelevantDate(const Datetime& dt) { return dt.startOfWeek(); } + + static void setupTableDates(Table& view) { + view.add(STRING_CMD_HISTORY_YEAR, true); + view.add(STRING_CMD_HISTORY_MONTH, true); + view.add(STRING_CMD_HISTORY_DAY, false); } - static void setupTableDates (Table& view) - { - view.add (STRING_CMD_HISTORY_YEAR, true); - view.add (STRING_CMD_HISTORY_MONTH, true); - view.add (STRING_CMD_HISTORY_DAY, false); - } - - static void insertRowDate ( - Table& view, - int row, - time_t rowTime, - time_t lastTime) - { - Datetime dt (rowTime); + static void insertRowDate(Table& view, int row, time_t rowTime, time_t lastTime) { + Datetime dt(rowTime); int m, d, y; - dt.toYMD (y, m, d); + dt.toYMD(y, m, d); - Datetime last_dt (lastTime); + Datetime last_dt(lastTime); int last_m, last_d, last_y; - last_dt.toYMD (last_y, last_m, last_d); + last_dt.toYMD(last_y, last_m, last_d); bool y_changed = (y != last_y) || (lastTime == 0); bool m_changed = (m != last_m) || (lastTime == 0); - if (y_changed) - view.set (row, 0, y); + if (y_changed) view.set(row, 0, y); - if (y_changed || m_changed) - view.set (row, 1, Datetime::monthName (m)); + if (y_changed || m_changed) view.set(row, 1, Datetime::monthName(m)); - view.set (row, 2, d); + view.set(row, 2, d); } - static constexpr const char* keyword = "ghistory.weekly"; - static constexpr const char* usage = "task ghistory.weekly"; - static constexpr const char* description = "Shows a graphical report of task history, by week"; + static constexpr const char* keyword = "ghistory.weekly"; + static constexpr const char* usage = "task ghistory.weekly"; + static constexpr const char* description = "Shows a graphical report of task history, by week"; static constexpr unsigned int dateFieldCount = 3; - static constexpr bool graphical = true; - static constexpr unsigned int labelWidth = 19; // length '2017 September Day ' = 19 + static constexpr bool graphical = true; + static constexpr unsigned int labelWidth = 19; // length '2017 September Day ' = 19 }; - // Explicit instantiations, avoiding cpp-inclusion or implementation in header template class CmdHistoryBase; template class CmdHistoryBase; diff --git a/src/commands/CmdHistory.h b/src/commands/CmdHistory.h index 311620cf7..f76683442 100644 --- a/src/commands/CmdHistory.h +++ b/src/commands/CmdHistory.h @@ -27,27 +27,27 @@ #ifndef INCLUDED_CMDHISTORY #define INCLUDED_CMDHISTORY -#include #include -#include #include +#include -template -class CmdHistoryBase : public Command -{ -public: - CmdHistoryBase (); - int execute (std::string&); +#include -private: - std::map groups; // Represents any timeinterval with data - std::map addedGroup; // Additions by timeinterval - std::map completedGroup; // Completions by timeinterval - std::map deletedGroup; // Deletions by timeinterval +template +class CmdHistoryBase : public Command { + public: + CmdHistoryBase(); + int execute(std::string&); + + private: + std::map groups; // Represents any timeinterval with data + std::map addedGroup; // Additions by timeinterval + std::map completedGroup; // Completions by timeinterval + std::map deletedGroup; // Deletions by timeinterval int rc; - void outputTabular (std::string&); - void outputGraphical (std::string&); + void outputTabular(std::string&); + void outputGraphical(std::string&); }; // Forward-declare strategies implemented in CmdHistory.cpp @@ -61,13 +61,13 @@ class AnnualHistoryStrategy; class AnnualGHistoryStrategy; // typedef the templates to nice names to be used outside this class -typedef CmdHistoryBase CmdHistoryDaily; -typedef CmdHistoryBase CmdGHistoryDaily; -typedef CmdHistoryBase CmdHistoryWeekly; -typedef CmdHistoryBase CmdGHistoryWeekly; -typedef CmdHistoryBase CmdHistoryMonthly; +typedef CmdHistoryBase CmdHistoryDaily; +typedef CmdHistoryBase CmdGHistoryDaily; +typedef CmdHistoryBase CmdHistoryWeekly; +typedef CmdHistoryBase CmdGHistoryWeekly; +typedef CmdHistoryBase CmdHistoryMonthly; typedef CmdHistoryBase CmdGHistoryMonthly; -typedef CmdHistoryBase CmdHistoryAnnual; -typedef CmdHistoryBase CmdGHistoryAnnual; +typedef CmdHistoryBase CmdHistoryAnnual; +typedef CmdHistoryBase CmdGHistoryAnnual; #endif diff --git a/src/commands/CmdIDs.cpp b/src/commands/CmdIDs.cpp index 62d6739e0..aa6bc6921 100644 --- a/src/commands/CmdIDs.cpp +++ b/src/commands/CmdIDs.cpp @@ -25,52 +25,50 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include #include -#include #include +#include +#include + std::string zshColonReplacement = ","; //////////////////////////////////////////////////////////////////////////////// -CmdIDs::CmdIDs () -{ - _keyword = "ids"; - _usage = "task ids"; - _description = "Shows the IDs of matching tasks, as a range"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdIDs::CmdIDs() { + _keyword = "ids"; + _usage = "task ids"; + _description = "Shows the IDs of matching tasks, as a range"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdIDs::execute (std::string& output) -{ +int CmdIDs::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Find number of matching tasks. - std::vector ids; + std::vector ids; for (auto& task : filtered) - if (task.id) - ids.push_back (task.id); + if (task.id) ids.push_back(task.id); - std::sort (ids.begin (), ids.end ()); - output = compressIds (ids) + '\n'; + std::sort(ids.begin(), ids.end()); + output = compressIds(ids) + '\n'; - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } @@ -86,35 +84,25 @@ int CmdIDs::execute (std::string& output) // // 1,3-4,6-9,11 // -std::string CmdIDs::compressIds (const std::vector & ids) -{ +std::string CmdIDs::compressIds(const std::vector& ids) { std::stringstream result; auto range_start = 0; auto range_end = 0; - for (unsigned int i = 0; i < ids.size (); ++i) - { - if (i + 1 == ids.size ()) - { - if (result.str ().length ()) - result << ' '; + for (unsigned int i = 0; i < ids.size(); ++i) { + if (i + 1 == ids.size()) { + if (result.str().length()) result << ' '; if (range_start < range_end) result << ids[range_start] << '-' << ids[range_end]; else result << ids[range_start]; - } - else - { - if (ids[range_end] + 1 == ids[i + 1]) - { + } else { + if (ids[range_end] + 1 == ids[i + 1]) { ++range_end; - } - else - { - if (result.str ().length ()) - result << ' '; + } else { + if (result.str().length()) result << ' '; if (range_start < range_end) result << ids[range_start] << '-' << ids[range_end]; @@ -126,201 +114,178 @@ std::string CmdIDs::compressIds (const std::vector & ids) } } - return result.str (); + return result.str(); } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionIds::CmdCompletionIds () -{ - _keyword = "_ids"; - _usage = "task _ids"; - _description = "Shows the IDs of matching tasks, in the form of a list"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdCompletionIds::CmdCompletionIds() { + _keyword = "_ids"; + _usage = "task _ids"; + _description = "Shows the IDs of matching tasks, in the form of a list"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionIds::execute (std::string& output) -{ +int CmdCompletionIds::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - std::vector ids; + std::vector ids; for (auto& task : filtered) - if (task.getStatus () != Task::deleted && - task.getStatus () != Task::completed) - ids.push_back (task.id); + if (task.getStatus() != Task::deleted && task.getStatus() != Task::completed) + ids.push_back(task.id); - std::sort (ids.begin (), ids.end ()); - output = join ("\n", ids) + '\n'; + std::sort(ids.begin(), ids.end()); + output = join("\n", ids) + '\n'; - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdZshCompletionIds::CmdZshCompletionIds () -{ - _keyword = "_zshids"; - _usage = "task _zshids"; - _description = "Shows the IDs and descriptions of matching tasks"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdZshCompletionIds::CmdZshCompletionIds() { + _keyword = "_zshids"; + _usage = "task _zshids"; + _description = "Shows the IDs and descriptions of matching tasks"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdZshCompletionIds::execute (std::string& output) -{ +int CmdZshCompletionIds::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); std::stringstream out; for (auto& task : filtered) - if (task.getStatus () != Task::deleted && - task.getStatus () != Task::completed) - out << task.id - << ':' - << str_replace(task.get ("description"), ":", zshColonReplacement) + if (task.getStatus() != Task::deleted && task.getStatus() != Task::completed) + out << task.id << ':' << str_replace(task.get("description"), ":", zshColonReplacement) << '\n'; - output = out.str (); + output = out.str(); - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdUUIDs::CmdUUIDs () -{ - _keyword = "uuids"; - _usage = "task uuids"; - _description = "Shows the UUIDs of matching tasks, as a space-separated list"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdUUIDs::CmdUUIDs() { + _keyword = "uuids"; + _usage = "task uuids"; + _description = "Shows the UUIDs of matching tasks, as a space-separated list"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdUUIDs::execute (std::string& output) -{ +int CmdUUIDs::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - std::vector uuids; + std::vector uuids; uuids.reserve(filtered.size()); - for (auto& task : filtered) - uuids.push_back (task.get ("uuid")); + for (auto& task : filtered) uuids.push_back(task.get("uuid")); - std::sort (uuids.begin (), uuids.end ()); - output = join (" ", uuids) + '\n'; + std::sort(uuids.begin(), uuids.end()); + output = join(" ", uuids) + '\n'; - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionUuids::CmdCompletionUuids () -{ - _keyword = "_uuids"; - _usage = "task _uuids"; - _description = "Shows the UUIDs of matching tasks, as a list"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdCompletionUuids::CmdCompletionUuids() { + _keyword = "_uuids"; + _usage = "task _uuids"; + _description = "Shows the UUIDs of matching tasks, as a list"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionUuids::execute (std::string& output) -{ +int CmdCompletionUuids::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - std::vector uuids; + std::vector uuids; uuids.reserve(filtered.size()); - for (auto& task : filtered) - uuids.push_back (task.get ("uuid")); + for (auto& task : filtered) uuids.push_back(task.get("uuid")); - std::sort (uuids.begin (), uuids.end ()); - output = join ("\n", uuids) + '\n'; + std::sort(uuids.begin(), uuids.end()); + output = join("\n", uuids) + '\n'; - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdZshCompletionUuids::CmdZshCompletionUuids () -{ - _keyword = "_zshuuids"; - _usage = "task _zshuuids"; - _description = "Shows the UUIDs and descriptions of matching tasks"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdZshCompletionUuids::CmdZshCompletionUuids() { + _keyword = "_zshuuids"; + _usage = "task _zshuuids"; + _description = "Shows the UUIDs and descriptions of matching tasks"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdZshCompletionUuids::execute (std::string& output) -{ +int CmdZshCompletionUuids::execute(std::string& output) { // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); std::stringstream out; for (auto& task : filtered) - out << task.get ("uuid") - << ':' - << str_replace (task.get ("description"), ":", zshColonReplacement) + out << task.get("uuid") << ':' << str_replace(task.get("description"), ":", zshColonReplacement) << '\n'; - output = out.str (); + output = out.str(); - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } diff --git a/src/commands/CmdIDs.h b/src/commands/CmdIDs.h index 6a345c2df..963b1e29e 100644 --- a/src/commands/CmdIDs.h +++ b/src/commands/CmdIDs.h @@ -27,52 +27,47 @@ #ifndef INCLUDED_CMDIDS #define INCLUDED_CMDIDS -#include #include -class CmdIDs : public Command -{ -public: - CmdIDs (); - int execute (std::string&); +#include -private: - std::string compressIds (const std::vector &); +class CmdIDs : public Command { + public: + CmdIDs(); + int execute(std::string&); + + private: + std::string compressIds(const std::vector&); }; -class CmdCompletionIds : public Command -{ -public: - CmdCompletionIds (); - int execute (std::string&); +class CmdCompletionIds : public Command { + public: + CmdCompletionIds(); + int execute(std::string&); }; -class CmdZshCompletionIds : public Command -{ -public: - CmdZshCompletionIds (); - int execute (std::string&); +class CmdZshCompletionIds : public Command { + public: + CmdZshCompletionIds(); + int execute(std::string&); }; -class CmdUUIDs : public Command -{ -public: - CmdUUIDs (); - int execute (std::string&); +class CmdUUIDs : public Command { + public: + CmdUUIDs(); + int execute(std::string&); }; -class CmdCompletionUuids : public Command -{ -public: - CmdCompletionUuids (); - int execute (std::string&); +class CmdCompletionUuids : public Command { + public: + CmdCompletionUuids(); + int execute(std::string&); }; -class CmdZshCompletionUuids : public Command -{ -public: - CmdZshCompletionUuids (); - int execute (std::string&); +class CmdZshCompletionUuids : public Command { + public: + CmdZshCompletionUuids(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdImport.cpp b/src/commands/CmdImport.cpp index 4bfe3072b..b3ccc0c2a 100644 --- a/src/commands/CmdImport.cpp +++ b/src/commands/CmdImport.cpp @@ -25,105 +25,107 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include -#include #include #include #include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdImport::CmdImport () -{ - _keyword = "import"; - _usage = "task import [ ...]"; - _description = "Imports JSON files"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdImport::CmdImport() { + _keyword = "import"; + _usage = "task import [ ...]"; + _description = "Imports JSON files"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::migration; + _category = Command::Category::migration; } //////////////////////////////////////////////////////////////////////////////// -int CmdImport::execute (std::string&) -{ +int CmdImport::execute(std::string&) { auto rc = 0; auto count = 0; // Get filenames from command line arguments. - auto words = Context::getContext ().cli2.getWords (); - if (! words.size () || - (words.size () == 1 && words[0] == "-")) - { - std::cout << format ("Importing '{1}'\n", "STDIN"); + auto words = Context::getContext().cli2.getWords(); + if (!words.size() || (words.size() == 1 && words[0] == "-")) { + std::cout << format("Importing '{1}'\n", "STDIN"); std::string json; std::string line; - while (std::getline (std::cin, line)) - json += line + '\n'; + while (std::getline(std::cin, line)) json += line + '\n'; - if (nontrivial (json)) - count = import (json); - } - else - { + if (nontrivial(json)) count = import(json); + } else { // Import tasks from all specified files. - for (auto& word : words) - { - File incoming (word); - if (! incoming.exists ()) - throw format ("File '{1}' not found.", word); + for (auto& word : words) { + File incoming(word); + if (!incoming.exists()) throw format("File '{1}' not found.", word); - std::cout << format ("Importing '{1}'\n", word); + std::cout << format("Importing '{1}'\n", word); // Load the file. std::string json; - incoming.read (json); - if (nontrivial (json)) - count += import (json); + incoming.read(json); + if (nontrivial(json)) count += import(json); } } - Context::getContext ().footnote (format ("Imported {1} tasks.", count)); + Context::getContext().footnote(format("Imported {1} tasks.", count)); + + // Warn the user about multiple occurrences of the same UUID. + auto duplicates = false; + for (const auto& [uuid, occurrences] : uuid_occurrences) { + if (occurrences > 1) { + duplicates = true; + Context::getContext().footnote( + format("Input contains UUID '{1}' {2} times.", uuid, occurrences)); + } + } + if (duplicates) + Context::getContext().footnote( + "Tasks with the same UUID have been merged. Please check the results."); + return rc; } //////////////////////////////////////////////////////////////////////////////// -int CmdImport::import (const std::string& input) -{ +int CmdImport::import(const std::string& input) { auto count = 0; - try - { - json::value* root = json::parse (input); - if (root) - { + try { + json::value* root = json::parse(input); + if (root) { // Single object parse. Input looks like: // { ... } - if (root->type () == json::j_object) - { + if (root->type() == json::j_object) { // For each object element... auto root_obj = (json::object*)root; - importSingleTask (root_obj); + importSingleTask(root_obj); ++count; } // Multiple object array. Input looks like: // [ { ... } , { ... } ] - else if (root->type () == json::j_array) - { + else if (root->type() == json::j_array) { auto root_arr = (json::array*)root; // For each object element... - for (auto& element : root_arr->_data) - { + for (auto& element : root_arr->_data) { // For each object element... auto root_obj = (json::object*)element; - importSingleTask (root_obj); + importSingleTask(root_obj); ++count; } } @@ -140,8 +142,7 @@ int CmdImport::import (const std::string& input) // Input looks like: // { ... } // { ... } - catch (std::string& e) - { + catch (std::string& e) { // Make a very cursory check for the old-style format. if (input[0] != '{') { throw e; @@ -149,21 +150,19 @@ int CmdImport::import (const std::string& input) // Read the entire file, so that errors do not result in a partial import. std::vector> objects; - for (auto& line : split (input, '\n')) - { - if (line.length ()) - { - json::value* root = json::parse (line); - if (root && root->type () == json::j_object) - objects.push_back (std::unique_ptr ((json::object *)root)); + for (auto& line : split(input, '\n')) { + if (line.length()) { + json::value* root = json::parse(line); + if (root && root->type() == json::j_object) + objects.push_back(std::unique_ptr((json::object*)root)); else - throw format ("Invalid JSON: {1}", line); + throw format("Invalid JSON: {1}", line); } } // Import the tasks. for (auto& root : objects) { - importSingleTask (root.get()); + importSingleTask(root.get()); ++count; } } @@ -172,22 +171,25 @@ int CmdImport::import (const std::string& input) } //////////////////////////////////////////////////////////////////////////////// -void CmdImport::importSingleTask (json::object* obj) -{ +void CmdImport::importSingleTask(json::object* obj) { // Parse the whole thing, validate the data. - Task task (obj); + Task task(obj); - auto hasGeneratedEntry = not task.has ("entry"); - auto hasExplicitEnd = task.has ("end"); + // An empty task is probably not intentional - at least a UUID should be included. + if (task.is_empty()) throw format("Cannot import an empty task."); - task.validate (); + auto hasGeneratedEntry = not task.has("entry"); + auto hasExplicitEnd = task.has("end"); - auto hasGeneratedEnd = not hasExplicitEnd and task.has ("end"); + task.validate(); + + auto hasGeneratedEnd = not hasExplicitEnd and task.has("end"); // Check whether the imported task is new or a modified existing task. Task before; - if (Context::getContext ().tdb2.get (task.get ("uuid"), before)) - { + auto uuid = task.get("uuid"); + uuid_occurrences[uuid]++; + if (Context::getContext().tdb2.get(uuid, before)) { // We need to neglect updates from attributes with dynamic defaults // unless they have been explicitly specified on import. // @@ -199,38 +201,28 @@ void CmdImport::importSingleTask (json::object* obj) // The 'modified' attribute is ignored in any case, since if it // were the only difference between the tasks, it would have been // neglected anyway, since it is bumped on each modification. - task.set ("modified", before.get ("modified")); + task.set("modified", before.get("modified")); // Other generated values are replaced by values from existing task, // so that they are ignored on comparison. - if (hasGeneratedEntry) - task.set ("entry", before.get ("entry")); + if (hasGeneratedEntry) task.set("entry", before.get("entry")); - if (hasGeneratedEnd) - task.set ("end", before.get ("end")); + if (hasGeneratedEnd) task.set("end", before.get("end")); - if (before != task) - { + if (before != task) { CmdModify modHelper; - modHelper.checkConsistency (before, task); - modHelper.modifyAndUpdate (before, task); + modHelper.checkConsistency(before, task); + modHelper.modifyAndUpdate(before, task); std::cout << " mod "; - } - else - { + } else { std::cout << " skip "; } - } - else - { - Context::getContext ().tdb2.add (task); + } else { + Context::getContext().tdb2.add(task); std::cout << " add "; } - std::cout << task.get ("uuid") - << ' ' - << task.get ("description") - << '\n'; + std::cout << task.get("uuid") << ' ' << task.get("description") << '\n'; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdImport.h b/src/commands/CmdImport.h index f492481ea..6ae736156 100644 --- a/src/commands/CmdImport.h +++ b/src/commands/CmdImport.h @@ -27,19 +27,21 @@ #ifndef INCLUDED_CMDIMPORT #define INCLUDED_CMDIMPORT -#include #include #include -class CmdImport : public Command -{ -public: - CmdImport (); - int execute (std::string&); +#include +#include -private: - int import (const std::string&); - void importSingleTask (json::object*); +class CmdImport : public Command { + public: + CmdImport(); + int execute(std::string&); + + private: + std::unordered_map uuid_occurrences; + int import(const std::string&); + void importSingleTask(json::object*); }; #endif diff --git a/src/commands/CmdImportV2.cpp b/src/commands/CmdImportV2.cpp new file mode 100644 index 000000000..a4619fdce --- /dev/null +++ b/src/commands/CmdImportV2.cpp @@ -0,0 +1,136 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +CmdImportV2::CmdImportV2() { + _keyword = "import-v2"; + _usage = "task import-v2"; + _description = "Imports Taskwarrior v2.x files"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; + _accepts_modifications = false; + _accepts_miscellaneous = true; + _category = Command::Category::migration; +} + +//////////////////////////////////////////////////////////////////////////////// +int CmdImportV2::execute(std::string&) { + std::vector> task_data; + + std::string location = (Context::getContext().data_dir); + File pending_file = File(location + "/pending.data"); + if (pending_file.exists()) { + TF2 pending_tf; + pending_tf.target(pending_file); + auto& pending_tasks = pending_tf.get_tasks(); + task_data.insert(task_data.end(), pending_tasks.begin(), pending_tasks.end()); + } + File completed_file = File(location + "/completed.data"); + if (completed_file.exists()) { + TF2 completed_tf; + completed_tf.target(completed_file); + auto& completed_tasks = completed_tf.get_tasks(); + task_data.insert(task_data.end(), completed_tasks.begin(), completed_tasks.end()); + } + + auto count = import(task_data); + + Context::getContext().footnote( + format("Imported {1} tasks from `*.data` files. You may now delete these files.", count)); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +int CmdImportV2::import(const std::vector>& task_data) { + auto count = 0; + const std::string uuid_key = "uuid"; + const std::string id_key = "id"; + const std::string descr_key = "description"; + auto& replica = Context::getContext().tdb2.replica(); + rust::Vec ops; + tc::add_undo_point(ops); + + for (auto& task : task_data) { + auto uuid_iter = task.find(uuid_key); + if (uuid_iter == task.end()) { + std::cout << " err - Task with no UUID\n"; + continue; + } + auto uuid_str = uuid_iter->second; + auto uuid = tc::uuid_from_string(uuid_str); + + bool added_task = false; + auto maybe_task_data = replica->get_task_data(uuid); + auto task_data = maybe_task_data.is_some() ? maybe_task_data.take() : [&]() { + added_task = true; + return tc::create_task(uuid, ops); + }(); + + for (auto& attr : task) { + if (attr.first == uuid_key || attr.first == id_key) { + continue; + } + task_data->update(attr.first, attr.second, ops); + } + count++; + + if (added_task) { + std::cout << " add "; + } else { + std::cout << " mod "; + } + std::cout << uuid_str << ' '; + if (auto descr_iter = task.find(descr_key); descr_iter != task.end()) { + std::cout << descr_iter->second; + } else { + std::cout << "(no description)"; + } + std::cout << "\n"; + } + + replica->commit_operations(std::move(ops)); + return count; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/ffi.h b/src/commands/CmdImportV2.h similarity index 78% rename from src/tc/ffi.h rename to src/commands/CmdImportV2.h index 213fc4074..5cfddd63d 100644 --- a/src/tc/ffi.h +++ b/src/commands/CmdImportV2.h @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // -// Copyright 2022, Dustin J. Mitchell, Tomas Babej, Paul Beckingham, Federico Hernandez. +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,13 +24,22 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDED_TC_FFI -#define INCLUDED_TC_FFI +#ifndef INCLUDED_CMDIMPORTV2 +#define INCLUDED_CMDIMPORTV2 -// The entire FFI API is embedded in the `tc::ffi` namespace -namespace tc::ffi { -#include -} +#include +#include + +#include + +class CmdImportV2 : public Command { + public: + CmdImportV2(); + int execute(std::string &); + + private: + int import(const std::vector> &task_data); +}; #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdInfo.cpp b/src/commands/CmdInfo.cpp index f0a1e993c..b3b3191fc 100644 --- a/src/commands/CmdInfo.cpp +++ b/src/commands/CmdInfo.cpp @@ -25,295 +25,284 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include -#include #include #include -#include -#include -#include -#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdInfo::CmdInfo () -{ - _keyword = "information"; - _usage = "task information"; - _description = "Shows all data and metadata"; - _read_only = true; +CmdInfo::CmdInfo() { + _keyword = "information"; + _usage = "task information"; + _description = "Shows all data and metadata"; + _read_only = true; // This is inaccurate, but it does prevent a GC. While this doesn't make a // lot of sense, given that the info command shows the ID, it does mimic the // behavior of versions prior to 2.0, which the test suite relies upon. // // Once the test suite is completely modified, this can be corrected. - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdInfo::execute (std::string& output) -{ +int CmdInfo::execute(std::string& output) { auto rc = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - if (! filtered.size ()) - { - Context::getContext ().footnote ("No matches."); + if (!filtered.size()) { + Context::getContext().footnote("No matches."); rc = 1; } // Determine the output date format, which uses a hierarchy of definitions. // rc.dateformat.info // rc.dateformat - auto dateformat = Context::getContext ().config.get ("dateformat.info"); - if (dateformat == "") - dateformat = Context::getContext ().config.get ("dateformat"); + auto dateformat = Context::getContext().config.get("dateformat.info"); + if (dateformat == "") dateformat = Context::getContext().config.get("dateformat"); - auto dateformatanno = Context::getContext ().config.get ("dateformat.annotation"); - if (dateformatanno == "") - dateformatanno = dateformat; + auto dateformatanno = Context::getContext().config.get("dateformat.annotation"); + if (dateformatanno == "") dateformatanno = dateformat; // Render each task. std::stringstream out; - for (auto& task : filtered) - { + for (auto& task : filtered) { Table view; - view.width (Context::getContext ().getWidth ()); - if (Context::getContext ().config.getBoolean ("obfuscate")) - view.obfuscate (); - if (Context::getContext ().color ()) - view.forceColor (); - view.add ("Name"); - view.add ("Value"); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + if (Context::getContext().config.getBoolean("obfuscate")) view.obfuscate(); + if (Context::getContext().color()) view.forceColor(); + view.add("Name"); + view.add("Value"); + setHeaderUnderline(view); Datetime now; // id - auto row = view.addRow (); - view.set (row, 0, "ID"); - view.set (row, 1, (task.id ? format (task.id) : "-")); + auto row = view.addRow(); + view.set(row, 0, "ID"); + view.set(row, 1, (task.id ? format(task.id) : "-")); - std::string status = Lexer::ucFirst (Task::statusToText (task.getStatus ())); + std::string status = Lexer::ucFirst(Task::statusToText(task.getStatus())); // description Color c; - autoColorize (task, c); - auto description = task.get ("description"); - auto indent = Context::getContext ().config.getInteger ("indent.annotation"); + autoColorize(task, c); + auto description = task.get("description"); + auto indent = Context::getContext().config.getInteger("indent.annotation"); - for (auto& anno : task.getAnnotations ()) - description += '\n' - + std::string (indent, ' ') - + Datetime (anno.first.substr (11)).toString (dateformatanno) - + ' ' - + anno.second; + for (auto& anno : task.getAnnotations()) + description += '\n' + std::string(indent, ' ') + + Datetime(anno.first.substr(11)).toString(dateformatanno) + ' ' + anno.second; - row = view.addRow (); - view.set (row, 0, "Description"); - view.set (row, 1, description, c); + if (task.has("description")) { + row = view.addRow(); + view.set(row, 0, "Description"); + view.set(row, 1, description, c); + } // status - row = view.addRow (); - view.set (row, 0, "Status"); - view.set (row, 1, status); + row = view.addRow(); + view.set(row, 0, "Status"); + view.set(row, 1, status); // project - if (task.has ("project")) - { - row = view.addRow (); - view.set (row, 0, "Project"); - view.set (row, 1, task.get ("project")); + if (task.has("project")) { + row = view.addRow(); + view.set(row, 0, "Project"); + view.set(row, 1, task.get("project")); } // dependencies: blocked { - auto blocked = task.getDependencyTasks (); - if (blocked.size ()) - { + auto blocked = task.getDependencyTasks(); + if (blocked.size()) { std::stringstream message; - for (auto& block : blocked) - message << block.id << ' ' << block.get ("description") << '\n'; + for (auto& block : blocked) message << block.id << ' ' << block.get("description") << '\n'; - row = view.addRow (); - view.set (row, 0, "This task blocked by"); - view.set (row, 1, message.str ()); + row = view.addRow(); + view.set(row, 0, "This task blocked by"); + view.set(row, 1, message.str()); } } // dependencies: blocking { - auto blocking = task.getBlockedTasks (); - if (blocking.size ()) - { + auto blocking = task.getBlockedTasks(); + if (blocking.size()) { std::stringstream message; - for (auto& block : blocking) - message << block.id << ' ' << block.get ("description") << '\n'; + for (auto& block : blocking) message << block.id << ' ' << block.get("description") << '\n'; - row = view.addRow (); - view.set (row, 0, "This task is blocking"); - view.set (row, 1, message.str ()); + row = view.addRow(); + view.set(row, 0, "This task is blocking"); + view.set(row, 1, message.str()); } } // recur - if (task.has ("recur")) - { - row = view.addRow (); - view.set (row, 0, "Recurrence"); - view.set (row, 1, task.get ("recur")); + if (task.has("recur")) { + row = view.addRow(); + view.set(row, 0, "Recurrence"); + view.set(row, 1, task.get("recur")); } // parent // 2017-01-07: Deprecated in 2.6.0 - if (task.has ("parent")) - { - row = view.addRow (); - view.set (row, 0, "Parent task"); - view.set (row, 1, task.get ("parent")); + if (task.has("parent")) { + row = view.addRow(); + view.set(row, 0, "Parent task"); + view.set(row, 1, task.get("parent")); } // mask // 2017-01-07: Deprecated in 2.6.0 - if (task.has ("mask")) - { - row = view.addRow (); - view.set (row, 0, "Mask"); - view.set (row, 1, task.get ("mask")); + if (task.has("mask")) { + row = view.addRow(); + view.set(row, 0, "Mask"); + view.set(row, 1, task.get("mask")); } // imask // 2017-01-07: Deprecated in 2.6.0 - if (task.has ("imask")) - { - row = view.addRow (); - view.set (row, 0, "Mask Index"); - view.set (row, 1, task.get ("imask")); + if (task.has("imask")) { + row = view.addRow(); + view.set(row, 0, "Mask Index"); + view.set(row, 1, task.get("imask")); } // template - if (task.has ("template")) - { - row = view.addRow (); - view.set (row, 0, "Template task"); - view.set (row, 1, task.get ("template")); + if (task.has("template")) { + row = view.addRow(); + view.set(row, 0, "Template task"); + view.set(row, 1, task.get("template")); } // last - if (task.has ("last")) - { - row = view.addRow (); - view.set (row, 0, "Last instance"); - view.set (row, 1, task.get ("last")); + if (task.has("last")) { + row = view.addRow(); + view.set(row, 0, "Last instance"); + view.set(row, 1, task.get("last")); } // rtype - if (task.has ("rtype")) - { - row = view.addRow (); - view.set (row, 0, "Recurrence type"); - view.set (row, 1, task.get ("rtype")); + if (task.has("rtype")) { + row = view.addRow(); + view.set(row, 0, "Recurrence type"); + view.set(row, 1, task.get("rtype")); } // entry - row = view.addRow (); - view.set (row, 0, "Entered"); - Datetime dt (task.get_date ("entry")); - std::string entry = dt.toString (dateformat); + if (task.has("entry") && task.get_date("entry")) { + row = view.addRow(); + view.set(row, 0, "Entered"); + Datetime dt(task.get_date("entry")); + std::string entry = dt.toString(dateformat); - std::string age; - auto created = task.get ("entry"); - if (created.length ()) - { - Datetime dt (strtoll (created.c_str (), nullptr, 10)); - age = Duration (now - dt).formatVague (); + std::string age; + auto created = task.get("entry"); + if (created.length()) { + Datetime dt(strtoll(created.c_str(), nullptr, 10)); + age = Duration(now - dt).formatVague(); + } + + view.set(row, 1, entry + " (" + age + ')'); } - view.set (row, 1, entry + " (" + age + ')'); + auto validDate = [&](const char* prop) { + if (!task.has(prop)) { + return false; + } + if (task.get_date(prop) == 0) { + return false; + } + return true; + }; // wait - if (task.has ("wait")) - { - row = view.addRow (); - view.set (row, 0, "Waiting until"); - view.set (row, 1, Datetime (task.get_date ("wait")).toString (dateformat)); + if (validDate("wait")) { + row = view.addRow(); + view.set(row, 0, "Waiting until"); + view.set(row, 1, Datetime(task.get_date("wait")).toString(dateformat)); } // scheduled - if (task.has ("scheduled")) - { - row = view.addRow (); - view.set (row, 0, "Scheduled"); - view.set (row, 1, Datetime (task.get_date ("scheduled")).toString (dateformat)); + if (validDate("scheduled")) { + row = view.addRow(); + view.set(row, 0, "Scheduled"); + view.set(row, 1, Datetime(task.get_date("scheduled")).toString(dateformat)); } // start - if (task.has ("start")) - { - row = view.addRow (); - view.set (row, 0, "Start"); - view.set (row, 1, Datetime (task.get_date ("start")).toString (dateformat)); + if (validDate("start")) { + row = view.addRow(); + view.set(row, 0, "Start"); + view.set(row, 1, Datetime(task.get_date("start")).toString(dateformat)); } // due (colored) - if (task.has ("due")) - { - row = view.addRow (); - view.set (row, 0, "Due"); - view.set (row, 1, Datetime (task.get_date ("due")).toString (dateformat)); + if (validDate("due")) { + row = view.addRow(); + view.set(row, 0, "Due"); + view.set(row, 1, Datetime(task.get_date("due")).toString(dateformat)); } // end - if (task.has ("end")) - { - row = view.addRow (); - view.set (row, 0, "End"); - view.set (row, 1, Datetime (task.get_date ("end")).toString (dateformat)); + if (validDate("end")) { + row = view.addRow(); + view.set(row, 0, "End"); + view.set(row, 1, Datetime(task.get_date("end")).toString(dateformat)); } // until - if (task.has ("until")) - { - row = view.addRow (); - view.set (row, 0, "Until"); - view.set (row, 1, Datetime (task.get_date ("until")).toString (dateformat)); + if (validDate("until")) { + row = view.addRow(); + view.set(row, 0, "Until"); + view.set(row, 1, Datetime(task.get_date("until")).toString(dateformat)); } // modified - if (task.has ("modified")) - { - row = view.addRow (); - view.set (row, 0, "Last modified"); + if (validDate("modified")) { + row = view.addRow(); + view.set(row, 0, "Last modified"); - Datetime mod (task.get_date ("modified")); - std::string age = Duration (now - mod).formatVague (); - view.set (row, 1, mod.toString (dateformat) + " (" + age + ')'); + Datetime mod(task.get_date("modified")); + std::string age = Duration(now - mod).formatVague(); + view.set(row, 1, mod.toString(dateformat) + " (" + age + ')'); } // tags ... - auto tags = task.getTags (); - if (tags.size ()) - { - auto allTags = join (" ", tags); + auto tags = task.getTags(); + if (tags.size()) { + auto allTags = join(" ", tags); - row = view.addRow (); - view.set (row, 0, "Tags"); - view.set (row, 1, allTags); + row = view.addRow(); + view.set(row, 0, "Tags"); + view.set(row, 1, allTags); } // Virtual tags. @@ -321,84 +310,79 @@ int CmdInfo::execute (std::string& output) // Note: This list must match that in Task::hasTag. // Note: This list must match that in ::feedback_reserved_tags. std::string virtualTags = ""; - if (task.hasTag ("ACTIVE")) virtualTags += "ACTIVE "; - if (task.hasTag ("ANNOTATED")) virtualTags += "ANNOTATED "; - if (task.hasTag ("BLOCKED")) virtualTags += "BLOCKED "; - if (task.hasTag ("BLOCKING")) virtualTags += "BLOCKING "; - if (task.hasTag ("CHILD")) virtualTags += "CHILD "; // 2017-01-07: Deprecated in 2.6.0 - if (task.hasTag ("COMPLETED")) virtualTags += "COMPLETED "; - if (task.hasTag ("DELETED")) virtualTags += "DELETED "; - if (task.hasTag ("DUE")) virtualTags += "DUE "; - if (task.hasTag ("DUETODAY")) virtualTags += "DUETODAY "; // 2016-03-29: Deprecated in 2.6.0 - if (task.hasTag ("INSTANCE")) virtualTags += "INSTANCE "; - if (task.hasTag ("LATEST")) virtualTags += "LATEST "; - if (task.hasTag ("MONTH")) virtualTags += "MONTH "; - if (task.hasTag ("ORPHAN")) virtualTags += "ORPHAN "; - if (task.hasTag ("OVERDUE")) virtualTags += "OVERDUE "; - if (task.hasTag ("PARENT")) virtualTags += "PARENT "; // 2017-01-07: Deprecated in 2.6.0 - if (task.hasTag ("PENDING")) virtualTags += "PENDING "; - if (task.hasTag ("PRIORITY")) virtualTags += "PRIORITY "; - if (task.hasTag ("PROJECT")) virtualTags += "PROJECT "; - if (task.hasTag ("QUARTER")) virtualTags += "QUARTER "; - if (task.hasTag ("READY")) virtualTags += "READY "; - if (task.hasTag ("SCHEDULED")) virtualTags += "SCHEDULED "; - if (task.hasTag ("TAGGED")) virtualTags += "TAGGED "; - if (task.hasTag ("TEMPLATE")) virtualTags += "TEMPLATE "; - if (task.hasTag ("TODAY")) virtualTags += "TODAY "; - if (task.hasTag ("TOMORROW")) virtualTags += "TOMORROW "; - if (task.hasTag ("UDA")) virtualTags += "UDA "; - if (task.hasTag ("UNBLOCKED")) virtualTags += "UNBLOCKED "; - if (task.hasTag ("UNTIL")) virtualTags += "UNTIL "; - if (task.hasTag ("WAITING")) virtualTags += "WAITING "; - if (task.hasTag ("WEEK")) virtualTags += "WEEK "; - if (task.hasTag ("YEAR")) virtualTags += "YEAR "; - if (task.hasTag ("YESTERDAY")) virtualTags += "YESTERDAY "; + if (task.hasTag("ACTIVE")) virtualTags += "ACTIVE "; + if (task.hasTag("ANNOTATED")) virtualTags += "ANNOTATED "; + if (task.hasTag("BLOCKED")) virtualTags += "BLOCKED "; + if (task.hasTag("BLOCKING")) virtualTags += "BLOCKING "; + if (task.hasTag("CHILD")) virtualTags += "CHILD "; // 2017-01-07: Deprecated in 2.6.0 + if (task.hasTag("COMPLETED")) virtualTags += "COMPLETED "; + if (task.hasTag("DELETED")) virtualTags += "DELETED "; + if (task.hasTag("DUE")) virtualTags += "DUE "; + if (task.hasTag("DUETODAY")) virtualTags += "DUETODAY "; // 2016-03-29: Deprecated in 2.6.0 + if (task.hasTag("INSTANCE")) virtualTags += "INSTANCE "; + if (task.hasTag("LATEST")) virtualTags += "LATEST "; + if (task.hasTag("MONTH")) virtualTags += "MONTH "; + if (task.hasTag("ORPHAN")) virtualTags += "ORPHAN "; + if (task.hasTag("OVERDUE")) virtualTags += "OVERDUE "; + if (task.hasTag("PARENT")) virtualTags += "PARENT "; // 2017-01-07: Deprecated in 2.6.0 + if (task.hasTag("PENDING")) virtualTags += "PENDING "; + if (task.hasTag("PRIORITY")) virtualTags += "PRIORITY "; + if (task.hasTag("PROJECT")) virtualTags += "PROJECT "; + if (task.hasTag("QUARTER")) virtualTags += "QUARTER "; + if (task.hasTag("READY")) virtualTags += "READY "; + if (task.hasTag("SCHEDULED")) virtualTags += "SCHEDULED "; + if (task.hasTag("TAGGED")) virtualTags += "TAGGED "; + if (task.hasTag("TEMPLATE")) virtualTags += "TEMPLATE "; + if (task.hasTag("TODAY")) virtualTags += "TODAY "; + if (task.hasTag("TOMORROW")) virtualTags += "TOMORROW "; + if (task.hasTag("UDA")) virtualTags += "UDA "; + if (task.hasTag("UNBLOCKED")) virtualTags += "UNBLOCKED "; + if (task.hasTag("UNTIL")) virtualTags += "UNTIL "; + if (task.hasTag("WAITING")) virtualTags += "WAITING "; + if (task.hasTag("WEEK")) virtualTags += "WEEK "; + if (task.hasTag("YEAR")) virtualTags += "YEAR "; + if (task.hasTag("YESTERDAY")) virtualTags += "YESTERDAY "; // If you update the above list, update src/Task.cpp and src/commands/CmdTags.cpp as well. - row = view.addRow (); - view.set (row, 0, "Virtual tags"); - view.set (row, 1, virtualTags); + row = view.addRow(); + view.set(row, 0, "Virtual tags"); + view.set(row, 1, virtualTags); } // uuid - row = view.addRow (); - view.set (row, 0, "UUID"); - auto uuid = task.get ("uuid"); - view.set (row, 1, uuid); + row = view.addRow(); + view.set(row, 0, "UUID"); + auto uuid = task.get("uuid"); + view.set(row, 1, uuid); // Task::urgency - row = view.addRow (); - view.set (row, 0, "Urgency"); - view.set (row, 1, Lexer::trimLeft (format (task.urgency (), 4, 4))); + row = view.addRow(); + view.set(row, 0, "Urgency"); + view.set(row, 1, Lexer::trimLeft(format(task.urgency(), 4, 4))); // Show any UDAs - auto all = task.all (); - for (auto& att: all) - { - if (Context::getContext ().columns.find (att) != Context::getContext ().columns.end ()) - { - Column* col = Context::getContext ().columns[att]; - if (col->is_uda ()) - { - auto value = task.get (att); - if (value != "") - { - row = view.addRow (); - view.set (row, 0, col->label ()); + auto all = task.all(); + for (auto& att : all) { + if (Context::getContext().columns.find(att) != Context::getContext().columns.end()) { + Column* col = Context::getContext().columns[att]; + if (col->is_uda()) { + auto value = task.get(att); + if (value != "") { + row = view.addRow(); + view.set(row, 0, col->label()); - if (col->type () == "date") - value = Datetime (value).toString (dateformat); - else if (col->type () == "duration") - { + if (col->type() == "date") + value = Datetime(value).toString(dateformat); + else if (col->type() == "duration") { Duration iso; std::string::size_type cursor = 0; - if (iso.parse (value, cursor)) - value = (std::string) Variant (iso.toTime_t (), Variant::type_duration); + if (iso.parse(value, cursor)) + value = (std::string)Variant(iso.toTime_t(), Variant::type_duration); else value = "PT0S"; } - view.set (row, 1, value); + view.set(row, 1, value); } } } @@ -406,156 +390,298 @@ int CmdInfo::execute (std::string& output) // Show any orphaned UDAs, which are identified by not being represented in // the context.columns map. - for (auto& att : all) - { - if (att.substr (0, 11) != "annotation_" && - att.substr (0, 5) != "tags_" && - att.substr (0, 4) != "dep_" && - Context::getContext ().columns.find (att) == Context::getContext ().columns.end ()) - { - row = view.addRow (); - view.set (row, 0, '[' + att); - view.set (row, 1, task.get (att) + ']'); + for (auto& att : all) { + if (att.substr(0, 11) != "annotation_" && att.substr(0, 4) != "tag_" && + att.substr(0, 4) != "dep_" && + Context::getContext().columns.find(att) == Context::getContext().columns.end()) { + row = view.addRow(); + view.set(row, 0, '[' + att); + view.set(row, 1, task.get(att) + ']'); } } // Create a second table, containing urgency details, if necessary. Table urgencyDetails; - if (task.urgency () != 0.0) - { - setHeaderUnderline (urgencyDetails); - if (Context::getContext ().color ()) - { - Color alternate (Context::getContext ().config.get ("color.alternate")); - urgencyDetails.colorOdd (alternate); - urgencyDetails.intraColorOdd (alternate); + if (task.urgency() != 0.0) { + setHeaderUnderline(urgencyDetails); + if (Context::getContext().color()) { + Color alternate(Context::getContext().config.get("color.alternate")); + urgencyDetails.colorOdd(alternate); + urgencyDetails.intraColorOdd(alternate); } - if (Context::getContext ().config.getBoolean ("obfuscate")) - urgencyDetails.obfuscate (); - if (Context::getContext ().config.getBoolean ("color")) - view.forceColor (); + if (Context::getContext().config.getBoolean("obfuscate")) urgencyDetails.obfuscate(); + if (Context::getContext().config.getBoolean("color")) view.forceColor(); - urgencyDetails.width (Context::getContext ().getWidth ()); - urgencyDetails.add (""); // Attribute - urgencyDetails.add (""); // Value - urgencyDetails.add (""); // * - urgencyDetails.add (""); // Coefficient - urgencyDetails.add (""); // = - urgencyDetails.add (""); // Result + urgencyDetails.width(Context::getContext().getWidth()); + urgencyDetails.add(""); // Attribute + urgencyDetails.add(""); // Value + urgencyDetails.add(""); // * + urgencyDetails.add(""); // Coefficient + urgencyDetails.add(""); // = + urgencyDetails.add(""); // Result - urgencyTerm (urgencyDetails, "project", task.urgency_project (), Task::urgencyProjectCoefficient); - urgencyTerm (urgencyDetails, "active", task.urgency_active (), Task::urgencyActiveCoefficient); - urgencyTerm (urgencyDetails, "scheduled", task.urgency_scheduled (), Task::urgencyScheduledCoefficient); - urgencyTerm (urgencyDetails, "waiting", task.urgency_waiting (), Task::urgencyWaitingCoefficient); - urgencyTerm (urgencyDetails, "blocked", task.urgency_blocked (), Task::urgencyBlockedCoefficient); - urgencyTerm (urgencyDetails, "blocking", task.urgency_blocking (), Task::urgencyBlockingCoefficient); - urgencyTerm (urgencyDetails, "annotations", task.urgency_annotations (), Task::urgencyAnnotationsCoefficient); - urgencyTerm (urgencyDetails, "tags", task.urgency_tags (), Task::urgencyTagsCoefficient); - urgencyTerm (urgencyDetails, "due", task.urgency_due (), Task::urgencyDueCoefficient); - urgencyTerm (urgencyDetails, "age", task.urgency_age (), Task::urgencyAgeCoefficient); + urgencyTerm(urgencyDetails, "project", task.urgency_project(), + Task::urgencyProjectCoefficient); + urgencyTerm(urgencyDetails, "active", task.urgency_active(), Task::urgencyActiveCoefficient); + urgencyTerm(urgencyDetails, "scheduled", task.urgency_scheduled(), + Task::urgencyScheduledCoefficient); + urgencyTerm(urgencyDetails, "waiting", task.urgency_waiting(), + Task::urgencyWaitingCoefficient); + urgencyTerm(urgencyDetails, "blocked", task.urgency_blocked(), + Task::urgencyBlockedCoefficient); + urgencyTerm(urgencyDetails, "blocking", task.urgency_blocking(), + Task::urgencyBlockingCoefficient); + urgencyTerm(urgencyDetails, "annotations", task.urgency_annotations(), + Task::urgencyAnnotationsCoefficient); + urgencyTerm(urgencyDetails, "tags", task.urgency_tags(), Task::urgencyTagsCoefficient); + urgencyTerm(urgencyDetails, "due", task.urgency_due(), Task::urgencyDueCoefficient); + urgencyTerm(urgencyDetails, "age", task.urgency_age(), Task::urgencyAgeCoefficient); // Tag, Project- and UDA-specific coefficients. - for (auto& var : Task::coefficients) - { - if (var.first.substr (0, 13) == "urgency.user.") - { + for (auto& var : Task::coefficients) { + if (var.first.substr(0, 13) == "urgency.user.") { // urgency.user.project..coefficient auto end = std::string::npos; - if (var.first.substr (13, 8) == "project." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - auto project = var.first.substr (21, end - 21); + if (var.first.substr(13, 8) == "project." && + (end = var.first.find(".coefficient")) != std::string::npos) { + auto project = var.first.substr(21, end - 21); const std::string taskProjectName = task.get("project"); - if (taskProjectName == project || - taskProjectName.find(project + '.') == 0) - { - urgencyTerm (urgencyDetails, "PROJECT " + project, 1.0, var.second); + if (taskProjectName == project || taskProjectName.find(project + '.') == 0) { + urgencyTerm(urgencyDetails, "PROJECT " + project, 1.0, var.second); } } // urgency.user.tag..coefficient - if (var.first.substr (13, 4) == "tag." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - auto name = var.first.substr (17, end - 17); - if (task.hasTag (name)) - urgencyTerm (urgencyDetails, "TAG " + name, 1.0, var.second); + if (var.first.substr(13, 4) == "tag." && + (end = var.first.find(".coefficient")) != std::string::npos) { + auto name = var.first.substr(17, end - 17); + if (task.hasTag(name)) urgencyTerm(urgencyDetails, "TAG " + name, 1.0, var.second); } // urgency.user.keyword..coefficient - if (var.first.substr (13, 8) == "keyword." && - (end = var.first.find (".coefficient")) != std::string::npos) - { - auto keyword = var.first.substr (21, end - 21); - if (task.get ("description").find (keyword) != std::string::npos) - urgencyTerm (urgencyDetails, "KEYWORD " + keyword, 1.0, var.second); + if (var.first.substr(13, 8) == "keyword." && + (end = var.first.find(".coefficient")) != std::string::npos) { + auto keyword = var.first.substr(21, end - 21); + if (task.get("description").find(keyword) != std::string::npos) + urgencyTerm(urgencyDetails, "KEYWORD " + keyword, 1.0, var.second); } } // urgency.uda..coefficient - else if (var.first.substr (0, 12) == "urgency.uda.") - { + else if (var.first.substr(0, 12) == "urgency.uda.") { // urgency.uda..coefficient // urgency.uda...coefficient - auto end = var.first.find (".coefficient"); - if (end != std::string::npos) - { - auto uda = var.first.substr (12, end - 12); - auto dot = uda.find ('.'); - if (dot == std::string::npos) - { + auto end = var.first.find(".coefficient"); + if (end != std::string::npos) { + auto uda = var.first.substr(12, end - 12); + auto dot = uda.find('.'); + if (dot == std::string::npos) { // urgency.uda..coefficient - if (task.has (uda)) - urgencyTerm (urgencyDetails, std::string ("UDA ") + uda, 1.0, var.second); - } - else - { + if (task.has(uda)) + urgencyTerm(urgencyDetails, std::string("UDA ") + uda, 1.0, var.second); + } else { // urgency.uda...coefficient - if (task.get (uda.substr(0, dot)) == uda.substr(dot+1)) - urgencyTerm (urgencyDetails, std::string ("UDA ") + uda, 1.0, var.second); + if (task.get(uda.substr(0, dot)) == uda.substr(dot + 1)) + urgencyTerm(urgencyDetails, std::string("UDA ") + uda, 1.0, var.second); } } } } - row = urgencyDetails.addRow (); - urgencyDetails.set (row, 5, rightJustify ("------", 6)); - row = urgencyDetails.addRow (); - urgencyDetails.set (row, 5, rightJustify (format (task.urgency (), 4, 4), 6)); + row = urgencyDetails.addRow(); + urgencyDetails.set(row, 5, rightJustify("------", 6)); + row = urgencyDetails.addRow(); + urgencyDetails.set(row, 5, rightJustify(format(task.urgency(), 4, 4), 6)); } - out << optionalBlankLine () - << view.render () - << '\n'; + // Create a third table, containing undo log change details. + Table journal; + setHeaderUnderline(journal); - if (urgencyDetails.rows () > 0) - out << urgencyDetails.render () - << '\n'; + if (Context::getContext().config.getBoolean("obfuscate")) journal.obfuscate(); + if (Context::getContext().config.getBoolean("color")) journal.forceColor(); + + journal.width(Context::getContext().getWidth()); + journal.add("Date"); + journal.add("Modification"); + + if (Context::getContext().config.getBoolean("journal.info")) { + auto& replica = Context::getContext().tdb2.replica(); + tc::Uuid tcuuid = tc::uuid_from_string(uuid); + auto tcoperations = replica->get_task_operations(tcuuid); + auto operations = Operation::operations(tcoperations); + + // Sort by type (Create < Update < Delete < UndoPoint) and then by timestamp. + std::sort(operations.begin(), operations.end()); + + long last_timestamp = 0; + for (size_t i = 0; i < operations.size(); i++) { + auto& op = operations[i]; + + // Only display updates -- creation and deletion aren't interesting. + if (!op.is_update()) { + continue; + } + + // Group operations that occur within 1s of this one. This is a heuristic + // for operations performed in the same `task` invocation, and allows e.g., + // `task done end:-2h` to take the updated `end` value into account. It also + // groups these events into a single "row" of the table for better layout. + size_t group_start = i; + for (i++; i < operations.size(); i++) { + auto& op2 = operations[i]; + if (!op2.is_update() || op2.get_timestamp() - op.get_timestamp() > 1) { + break; + } + } + size_t group_end = i; + i--; + + std::optional msg = + formatForInfo(operations, group_start, group_end, dateformat, last_timestamp); + + if (!msg) { + continue; + } + + int row = journal.addRow(); + Datetime timestamp(op.get_timestamp()); + journal.set(row, 0, timestamp.toString(dateformat)); + journal.set(row, 1, *msg); + } + } + + out << optionalBlankLine() << view.render() << '\n'; + + if (urgencyDetails.rows() > 0) out << urgencyDetails.render() << '\n'; + + if (journal.rows() > 0) out << journal.render() << '\n'; } - output = out.str (); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -void CmdInfo::urgencyTerm ( - Table& view, - const std::string& label, - float measure, - float coefficient) const -{ +void CmdInfo::urgencyTerm(Table& view, const std::string& label, float measure, + float coefficient) const { auto value = measure * coefficient; - if (value != 0.0) - { - auto row = view.addRow (); - view.set (row, 0, " " + label); - view.set (row, 1, rightJustify (format (measure, 5, 3), 6)); - view.set (row, 2, "*"); - view.set (row, 3, rightJustify (format (coefficient, 4, 2), 4)); - view.set (row, 4, "="); - view.set (row, 5, rightJustify (format (value, 5, 3), 6)); + if (value != 0.0) { + auto row = view.addRow(); + view.set(row, 0, " " + label); + view.set(row, 1, rightJustify(format(measure, 5, 3), 6)); + view.set(row, 2, "*"); + view.set(row, 3, rightJustify(format(coefficient, 4, 2), 4)); + view.set(row, 4, "="); + view.set(row, 5, rightJustify(format(value, 5, 3), 6)); } } //////////////////////////////////////////////////////////////////////////////// +std::optional CmdInfo::formatForInfo(const std::vector& operations, + size_t group_start, size_t group_end, + const std::string& dateformat, long& last_start) { + std::stringstream out; + for (auto i = group_start; i < group_end; i++) { + auto& operation = operations[i]; + assert(operation.is_update()); + + // Extract the parts of the Update operation. + std::string prop = operation.get_property(); + std::optional value = operation.get_value(); + std::optional old_value = operation.get_old_value(); + Datetime timestamp(operation.get_timestamp()); + + // Never care about modifying the modification time, or the legacy properties `depends` and + // `tags`. + if (prop == "modified" || prop == "depends" || prop == "tags") { + continue; + } + + // Handle property deletions + if (!value && old_value) { + if (Task::isAnnotationAttr(prop)) { + out << format("Annotation '{1}' deleted.\n", *old_value); + } else if (Task::isTagAttr(prop)) { + out << format("Tag '{1}' deleted.\n", Task::attr2Tag(prop)); + } else if (Task::isDepAttr(prop)) { + out << format("Dependency on '{1}' deleted.\n", Task::attr2Dep(prop)); + } else if (prop == "start") { + Datetime started(last_start); + Datetime stopped = timestamp; + + // If any update in this group sets the `end` property, use that instead of the + // timestamp deleting the `start` property as the stop time. + // See https://github.com/GothenburgBitFactory/taskwarrior/issues/2514 + for (auto i = group_start; i < group_end; i++) { + auto& op = operations[i]; + assert(op.is_update()); + if (op.get_property() == "end") { + try { + stopped = op.get_value().value(); + } catch (std::string) { + // Fall back to the 'start' timestamp if its value is un-parseable. + stopped = op.get_timestamp(); + } + } + } + + out << format("{1} deleted (duration: {2}).", Lexer::ucFirst(prop), + Duration(stopped - started).format()) + << "\n"; + } else { + out << format("{1} deleted.\n", Lexer::ucFirst(prop)); + } + } + + // Handle property additions. + if (value && !old_value) { + if (Task::isAnnotationAttr(prop)) { + out << format("Annotation of '{1}' added.\n", *value); + } else if (Task::isTagAttr(prop)) { + out << format("Tag '{1}' added.\n", Task::attr2Tag(prop)); + } else if (Task::isDepAttr(prop)) { + out << format("Dependency on '{1}' added.\n", Task::attr2Dep(prop)); + } else { + // Record the last start time for later duration calculation. + if (prop == "start") { + try { + last_start = Datetime(value.value()).toEpoch(); + } catch (std::string) { + // ignore invalid dates + } + } + + out << format("{1} set to '{2}'.", Lexer::ucFirst(prop), + renderAttribute(prop, *value, dateformat)) + << "\n"; + } + } + + // Handle property changes. + if (value && old_value) { + if (Task::isTagAttr(prop) || Task::isDepAttr(prop)) { + // Dependencies and tags do not have meaningful values. + } else if (Task::isAnnotationAttr(prop)) { + out << format("Annotation changed to '{1}'.\n", *value); + } else { + // Record the last start time for later duration calculation. + if (prop == "start") { + last_start = Datetime(value.value()).toEpoch(); + } + + out << format("{1} changed from '{2}' to '{3}'.", Lexer::ucFirst(prop), + renderAttribute(prop, *old_value, dateformat), + renderAttribute(prop, *value, dateformat)) + << "\n"; + } + } + } + + if (out.str().length() == 0) return std::nullopt; + + return out.str(); +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdInfo.h b/src/commands/CmdInfo.h index ed3b647bd..196d8e8b0 100644 --- a/src/commands/CmdInfo.h +++ b/src/commands/CmdInfo.h @@ -27,18 +27,25 @@ #ifndef INCLUDED_CMDINFO #define INCLUDED_CMDINFO -#include #include +#include #include +#include -class CmdInfo : public Command -{ -public: - CmdInfo (); - int execute (std::string&); +#include +#include -private: - void urgencyTerm (Table&, const std::string&, float, float) const; +class CmdInfo : public Command { + public: + CmdInfo(); + int execute(std::string&); + + private: + void urgencyTerm(Table&, const std::string&, float, float) const; + // Format a group of update operations for display in `task info`. + std::optional formatForInfo(const std::vector& operations, + size_t group_start, size_t group_end, + const std::string& dateformat, long& last_start); }; #endif diff --git a/src/commands/CmdLog.cpp b/src/commands/CmdLog.cpp index 91043e195..489001e0c 100644 --- a/src/commands/CmdLog.cpp +++ b/src/commands/CmdLog.cpp @@ -25,50 +25,49 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include +#include #include -#include //////////////////////////////////////////////////////////////////////////////// -CmdLog::CmdLog () -{ - _keyword = "log"; - _usage = "task log "; - _description = "Adds a new task that is already completed"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = false; +CmdLog::CmdLog() { + _keyword = "log"; + _usage = "task log "; + _description = "Adds a new task that is already completed"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = false; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdLog::execute (std::string& output) -{ +int CmdLog::execute(std::string& output) { // Apply the command line modifications to the new task. Task task; - task.modify (Task::modReplace, true); - task.setStatus (Task::completed); + task.modify(Task::modReplace, true); + task.setStatus(Task::completed); // Cannot log recurring tasks. - if (task.has ("recur")) - throw std::string ("You cannot log recurring tasks."); + if (task.has("recur")) throw std::string("You cannot log recurring tasks."); // Cannot log waiting tasks. - if (task.has ("wait")) - throw std::string ("You cannot log waiting tasks."); + if (task.has("wait")) throw std::string("You cannot log waiting tasks."); - Context::getContext ().tdb2.add (task); + Context::getContext().tdb2.add(task); - if (Context::getContext ().verbose ("project")) - Context::getContext ().footnote (onProjectChange (task)); + if (Context::getContext().verbose("project")) + Context::getContext().footnote(onProjectChange(task)); - if (Context::getContext ().verbose ("new-uuid")) - output = format ("Logged task {1}.\n", task.get ("uuid")); + if (Context::getContext().verbose("new-uuid")) + output = format("Logged task {1}.\n", task.get("uuid")); return 0; } diff --git a/src/commands/CmdLog.h b/src/commands/CmdLog.h index b7b322671..f5561472f 100644 --- a/src/commands/CmdLog.h +++ b/src/commands/CmdLog.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDLOG #define INCLUDED_CMDLOG -#include #include -class CmdLog : public Command -{ -public: - CmdLog (); - int execute (std::string&); +#include + +class CmdLog : public Command { + public: + CmdLog(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdLogo.cpp b/src/commands/CmdLogo.cpp index a910f7e70..75db2c837 100644 --- a/src/commands/CmdLogo.cpp +++ b/src/commands/CmdLogo.cpp @@ -25,96 +25,89 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include #include //////////////////////////////////////////////////////////////////////////////// -CmdLogo::CmdLogo () -{ - _keyword = "logo"; - _usage = "task logo"; - _description = "Displays the Taskwarrior logo"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdLogo::CmdLogo() { + _keyword = "logo"; + _usage = "task logo"; + _description = "Displays the Taskwarrior logo"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdLogo::execute (std::string& output) -{ - static const char* data[] = - { - ".........ABDEF", - ".......BDILNMM", - ".....ACJNLHFEE", - "....AFMMFDEEEE", - "...AFOIDEEEEDD", - "..AFOHDEEEDIOR", - "..DMIDEEEEOXXX", - ".BJMDEEEDMXXXX", - ".ENFEEEEFVXXXX", - "BILDEEEEJXXXXX", - "DLHEEEEDLXXXXX", - "GMFEEEEDKXXXXX", - "GMEEEEEEGTIKOR", - "GMEEEEEEETODDD", - "GMEEEEEEETXRGE", - "GMEEEEEEFVXXSE", - "ENFEEEEEHWXXUE", - "CLHEEEEDLXXXUE", - "AILDEEEDRXXXUE", - ".DNFEEEEPXXXUE", - ".BJMDEEEEPXXUE", - "..DMIDEEEDLXUE", - "..AFOHDEEEDHME", - "...AFOIDEEEEDE", - "....AFMMFDEEEE", - ".....ACJNLHFEE", - ".......BDILNMM", - ".........ABDEF", - "" - }; +int CmdLogo::execute(std::string& output) { + static const char* data[] = {".........ABDEF", + ".......BDILNMM", + ".....ACJNLHFEE", + "....AFMMFDEEEE", + "...AFOIDEEEEDD", + "..AFOHDEEEDIOR", + "..DMIDEEEEOXXX", + ".BJMDEEEDMXXXX", + ".ENFEEEEFVXXXX", + "BILDEEEEJXXXXX", + "DLHEEEEDLXXXXX", + "GMFEEEEDKXXXXX", + "GMEEEEEEGTIKOR", + "GMEEEEEEETODDD", + "GMEEEEEEETXRGE", + "GMEEEEEEFVXXSE", + "ENFEEEEEHWXXUE", + "CLHEEEEDLXXXUE", + "AILDEEEDRXXXUE", + ".DNFEEEEPXXXUE", + ".BJMDEEEEPXXUE", + "..DMIDEEEDLXUE", + "..AFOHDEEEDHME", + "...AFOIDEEEEDE", + "....AFMMFDEEEE", + ".....ACJNLHFEE", + ".......BDILNMM", + ".........ABDEF", + ""}; - if (! Context::getContext ().color ()) - throw std::string ("The logo command requires that color support is enabled."); + if (!Context::getContext().color()) + throw std::string("The logo command requires that color support is enabled."); - std::string indent (Context::getContext ().config.getInteger ("indent.report"), ' '); - output += optionalBlankLine (); + std::string indent(Context::getContext().config.getInteger("indent.report"), ' '); + output += optionalBlankLine(); - for (int line = 0; data[line][0]; ++line) - { + for (int line = 0; data[line][0]; ++line) { output += indent; - for (int c = 0; c < 14; ++c) - { - int value = (int) data[line][c]; + for (int c = 0; c < 14; ++c) { + int value = (int)data[line][c]; if (value == '.') output += " "; - else - { + else { value += 167; - char block [24]; - snprintf (block, 24, "\033[48;5;%dm \033[0m", value); + char block[24]; + snprintf(block, 24, "\033[48;5;%dm \033[0m", value); output += block; } } - for (int c = 13; c >= 0; --c) - { + for (int c = 13; c >= 0; --c) { int value = data[line][c]; if (value == '.') output += " "; - else - { + else { value += 167; - char block [24]; - snprintf (block, 24, "\033[48;5;%dm \033[0m", value); + char block[24]; + snprintf(block, 24, "\033[48;5;%dm \033[0m", value); output += block; } } @@ -122,7 +115,7 @@ int CmdLogo::execute (std::string& output) output += '\n'; } - output += optionalBlankLine (); + output += optionalBlankLine(); return 0; } diff --git a/src/commands/CmdLogo.h b/src/commands/CmdLogo.h index 441ec123a..bea0bfecf 100644 --- a/src/commands/CmdLogo.h +++ b/src/commands/CmdLogo.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDLOGO #define INCLUDED_CMDLOGO -#include #include -class CmdLogo : public Command -{ -public: - CmdLogo (); - int execute (std::string&); +#include + +class CmdLogo : public Command { + public: + CmdLogo(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdModify.cpp b/src/commands/CmdModify.cpp index cb407367d..a962ea10b 100644 --- a/src/commands/CmdModify.cpp +++ b/src/commands/CmdModify.cpp @@ -25,198 +25,179 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include #include +#include #include -#define STRING_CMD_MODIFY_TASK_R "Modifying recurring task {1} '{2}'." -#define STRING_CMD_MODIFY_RECUR "This is a recurring task. Do you want to modify all pending recurrences of this same task?" +#include + +#define STRING_CMD_MODIFY_TASK_R "Modifying recurring task {1} '{2}'." +#define STRING_CMD_MODIFY_RECUR \ + "This is a recurring task. Do you want to modify all pending recurrences of this same task?" //////////////////////////////////////////////////////////////////////////////// -CmdModify::CmdModify () -{ - _keyword = "modify"; - _usage = "task modify "; - _description = "Modifies the existing task with provided arguments."; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = true; +CmdModify::CmdModify() { + _keyword = "modify"; + _usage = "task modify "; + _description = "Modifies the existing task with provided arguments."; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdModify::execute (std::string&) -{ +int CmdModify::execute(std::string &) { auto rc = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; auto count = 0; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); - task.modify (Task::modReplace); + for (auto &task : filtered) { + Task before(task); + task.modify(Task::modReplace); - if (before != task) - { + if (before != task) { // Abort if change introduces inconsistencies. checkConsistency(before, task); - auto question = format ("Modify task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + auto question = + format("Modify task {1} '{2}'?", task.identifier(true), task.get("description")); - if (permission (before.diff (task) + question, filtered.size ())) - { - count += modifyAndUpdate (before, task, &projectChanges); - } - else - { + if (permission(before.diff(task) + question, filtered.size())) { + count += modifyAndUpdate(before, task, &projectChanges); + } else { std::cout << "Task not modified.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } } } // Now list the project changes. - for (const auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + for (const auto &change : projectChanges) + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Modified {1} task." : "Modified {1} tasks.", count); + feedback_affected(count == 1 ? "Modified {1} task." : "Modified {1} tasks.", count); return rc; } //////////////////////////////////////////////////////////////////////////////// // TODO Why is this not in Task::validate? -void CmdModify::checkConsistency (Task &before, Task &after) -{ +void CmdModify::checkConsistency(Task &before, Task &after) { // Perform some logical consistency checks. - if (after.has ("recur") && - ! after.has ("due") && - ! before.has ("due")) - throw std::string ("You cannot specify a recurring task without a due date."); + if (after.has("recur") && !after.has("due") && !before.has("due")) + throw std::string("You cannot specify a recurring task without a due date."); - if (before.has ("recur") && - before.has ("due") && - (! after.has ("due") || - after.get ("due") == "")) - throw std::string ("You cannot remove the due date from a recurring task."); + if (before.has("recur") && before.has("due") && (!after.has("due") || after.get("due") == "")) + throw std::string("You cannot remove the due date from a recurring task."); - if (before.has ("recur") && - (! after.has ("recur") || - after.get ("recur") == "")) - throw std::string ("You cannot remove the recurrence from a recurring task."); + if (before.has("recur") && (!after.has("recur") || after.get("recur") == "")) + throw std::string("You cannot remove the recurrence from a recurring task."); + + if ((before.getStatus() == Task::pending) && (after.getStatus() == Task::pending) && + (before.get("end") == "") && (after.get("end") != "")) + throw format("Could not modify task {1}. You cannot set an end date on a pending task.", + before.identifier(true)); } //////////////////////////////////////////////////////////////////////////////// -int CmdModify::modifyAndUpdate ( - Task &before, Task &after, - std::map *projectChanges /* = NULL */) -{ +int CmdModify::modifyAndUpdate(Task &before, Task &after, + std::map *projectChanges /* = NULL */) { // This task. auto count = 1; - updateRecurrenceMask (after); - feedback_affected ("Modifying task {1} '{2}'.", after); - feedback_unblocked (after); - Context::getContext ().tdb2.modify (after); - if (Context::getContext ().verbose ("project") && projectChanges) - (*projectChanges)[after.get ("project")] = onProjectChange (before, after); + updateRecurrenceMask(after); + feedback_affected("Modifying task {1} '{2}'.", after); + feedback_unblocked(after); + Context::getContext().tdb2.modify(after); + if (Context::getContext().verbose("project") && projectChanges) + (*projectChanges)[after.get("project")] = onProjectChange(before, after); // Task has siblings - modify them. - if (after.has ("parent")) - count += modifyRecurrenceSiblings (after, projectChanges); + if (after.has("parent")) count += modifyRecurrenceSiblings(after, projectChanges); // Task has child tasks - modify them. - else if (after.get ("status") == "recurring") - count += modifyRecurrenceParent (after, projectChanges); + else if (after.get("status") == "recurring") + count += modifyRecurrenceParent(after, projectChanges); return count; } //////////////////////////////////////////////////////////////////////////////// -int CmdModify::modifyRecurrenceSiblings ( - Task &task, - std::map *projectChanges /* = NULL */) -{ +int CmdModify::modifyRecurrenceSiblings( + Task &task, std::map *projectChanges /* = NULL */) { auto count = 0; - if ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm (STRING_CMD_MODIFY_RECUR)) || - Context::getContext ().config.getBoolean ("recurrence.confirmation")) - { - std::vector siblings = Context::getContext ().tdb2.siblings (task); - for (auto& sibling : siblings) - { - Task alternate (sibling); - sibling.modify (Task::modReplace); - updateRecurrenceMask (sibling); + if ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm(STRING_CMD_MODIFY_RECUR)) || + Context::getContext().config.getBoolean("recurrence.confirmation")) { + std::vector siblings = Context::getContext().tdb2.siblings(task); + for (auto &sibling : siblings) { + Task alternate(sibling); + sibling.modify(Task::modReplace); + updateRecurrenceMask(sibling); ++count; - feedback_affected (STRING_CMD_MODIFY_TASK_R, sibling); - feedback_unblocked (sibling); - Context::getContext ().tdb2.modify (sibling); - if (Context::getContext ().verbose ("project") && projectChanges) - (*projectChanges)[sibling.get ("project")] = onProjectChange (alternate, sibling); + feedback_affected(STRING_CMD_MODIFY_TASK_R, sibling); + feedback_unblocked(sibling); + Context::getContext().tdb2.modify(sibling); + if (Context::getContext().verbose("project") && projectChanges) + (*projectChanges)[sibling.get("project")] = onProjectChange(alternate, sibling); } // Modify the parent Task parent; - Context::getContext ().tdb2.get (task.get ("parent"), parent); - parent.modify (Task::modReplace); - Context::getContext ().tdb2.modify (parent); + Context::getContext().tdb2.get(task.get("parent"), parent); + parent.modify(Task::modReplace); + Context::getContext().tdb2.modify(parent); } return count; } //////////////////////////////////////////////////////////////////////////////// -int CmdModify::modifyRecurrenceParent ( - Task &task, - std::map *projectChanges /* = NULL */) -{ +int CmdModify::modifyRecurrenceParent( + Task &task, std::map *projectChanges /* = NULL */) { auto count = 0; - auto children = Context::getContext ().tdb2.children (task); - if (children.size () && - ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm (STRING_CMD_MODIFY_RECUR)) || - Context::getContext ().config.getBoolean ("recurrence.confirmation"))) - { - for (auto& child : children) - { - Task alternate (child); - child.modify (Task::modReplace); - updateRecurrenceMask (child); - Context::getContext ().tdb2.modify (child); - if (Context::getContext ().verbose ("project") && projectChanges) - (*projectChanges)[child.get ("project")] = onProjectChange (alternate, child); + auto children = Context::getContext().tdb2.children(task); + if (children.size() && + ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm(STRING_CMD_MODIFY_RECUR)) || + Context::getContext().config.getBoolean("recurrence.confirmation"))) { + for (auto &child : children) { + Task alternate(child); + child.modify(Task::modReplace); + updateRecurrenceMask(child); + Context::getContext().tdb2.modify(child); + if (Context::getContext().verbose("project") && projectChanges) + (*projectChanges)[child.get("project")] = onProjectChange(alternate, child); ++count; - feedback_affected (STRING_CMD_MODIFY_TASK_R, child); + feedback_affected(STRING_CMD_MODIFY_TASK_R, child); } } @@ -224,4 +205,3 @@ int CmdModify::modifyRecurrenceParent ( } //////////////////////////////////////////////////////////////////////////////// - diff --git a/src/commands/CmdModify.h b/src/commands/CmdModify.h index 1dcfb4fd5..17313e798 100644 --- a/src/commands/CmdModify.h +++ b/src/commands/CmdModify.h @@ -27,21 +27,21 @@ #ifndef INCLUDED_CMDMODIFY #define INCLUDED_CMDMODIFY -#include #include -class CmdModify : public Command -{ -public: - CmdModify (); - int execute (std::string&); - void checkConsistency (Task &before, Task &after); - int modifyAndUpdate (Task &before, Task &after, - std::map *projectChanges = nullptr); - int modifyRecurrenceSiblings (Task &task, - std::map *projectChanges = nullptr); - int modifyRecurrenceParent (Task &task, - std::map *projectChanges = nullptr); +#include + +class CmdModify : public Command { + public: + CmdModify(); + int execute(std::string &); + void checkConsistency(Task &before, Task &after); + int modifyAndUpdate(Task &before, Task &after, + std::map *projectChanges = nullptr); + int modifyRecurrenceSiblings(Task &task, + std::map *projectChanges = nullptr); + int modifyRecurrenceParent(Task &task, + std::map *projectChanges = nullptr); }; #endif diff --git a/src/commands/CmdNews.cpp b/src/commands/CmdNews.cpp index 5e22fbb5b..b6a6f75cb 100644 --- a/src/commands/CmdNews.cpp +++ b/src/commands/CmdNews.cpp @@ -25,21 +25,23 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include #include #include #include -#include +#include #include +#include #include -#include + +#include +#include +#include /* Adding a new version: - * + * * - Add a new `versionX_Y_Z` method to `NewsItem`, and add news items for the new * release. * - Call the new method in `NewsItem.all()`. Calls should be in version order. @@ -47,70 +49,58 @@ */ //////////////////////////////////////////////////////////////////////////////// -CmdNews::CmdNews () -{ - _keyword = "news"; - _usage = "task news"; - _description = "Displays news about the recent releases"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdNews::CmdNews() { + _keyword = "news"; + _usage = "task news"; + _description = "Displays news about the recent releases"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; - _accepts_miscellaneous = true; - _category = Command::Category::misc; + _accepts_miscellaneous = false; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -static void signal_handler (int s) -{ - if (s == SIGINT) - { +static void signal_handler(int s) { + if (s == SIGINT) { Color footnote; - if (Context::getContext ().color ()) { - if (Context::getContext ().config.has ("color.footnote")) - footnote = Color (Context::getContext ().config.get ("color.footnote")); + if (Context::getContext().color()) { + if (Context::getContext().config.has("color.footnote")) + footnote = Color(Context::getContext().config.get("color.footnote")); } std::cout << "\n\nCome back and read about new features later!\n"; - std::cout << footnote.colorize ( - "\nIf you enjoy Taskwarrior, please consider supporting the project at:\n" - " https://github.com/sponsors/GothenburgBitFactory/\n" - ); - exit (1); + std::cout << footnote.colorize( + "\nIf you enjoy Taskwarrior, please consider supporting the project at:\n" + " https://github.com/sponsors/GothenburgBitFactory/\n"); + exit(1); } } -void wait_for_enter () -{ - signal (SIGINT, signal_handler); +void wait_for_enter() { + signal(SIGINT, signal_handler); std::string dummy; std::cout << "\nPress enter to continue.."; - std::getline (std::cin, dummy); + std::getline(std::cin, dummy); std::cout << "\33[2K\033[A\33[2K"; // Erase current line, move up, and erase again - signal (SIGINT, SIG_DFL); + signal(SIGINT, SIG_DFL); } //////////////////////////////////////////////////////////////////////////////// // Holds information about single improvement / bug. // -NewsItem::NewsItem ( - Version version, - bool major, - const std::string& title, - const std::string& bg_title, - const std::string& background, - const std::string& punchline, - const std::string& update, - const std::string& reasoning, - const std::string& actions -) { +NewsItem::NewsItem(Version version, const std::string& title, const std::string& bg_title, + const std::string& background, const std::string& punchline, + const std::string& update, const std::string& reasoning, + const std::string& actions) { _version = version; - _major = major; _title = title; _bg_title = bg_title; _background = background; @@ -120,60 +110,57 @@ NewsItem::NewsItem ( _actions = actions; } -void NewsItem::render () { - auto config = Context::getContext ().config; +void NewsItem::render() { + auto config = Context::getContext().config; Color header; Color footnote; Color bold; Color underline; - if (Context::getContext ().color ()) { - bold = Color ("bold"); - underline = Color ("underline"); - if (config.has ("color.header")) - header = Color (config.get ("color.header")); - if (config.has ("color.footnote")) - footnote = Color (config.get ("color.footnote")); + if (Context::getContext().color()) { + bold = Color("bold"); + underline = Color("underline"); + if (config.has("color.header")) header = Color(config.get("color.header")); + if (config.has("color.footnote")) footnote = Color(config.get("color.footnote")); } // TODO: For some reason, bold cannot be blended in 256-color terminals // Apply this workaround of colorizing twice. - std::cout << bold.colorize (header.colorize (format ("{1} ({2})\n", _title, _version))); - if (_background.size ()) { - if (_bg_title.empty ()) - _bg_title = "Background"; + std::cout << bold.colorize(header.colorize(format("{1} ({2})\n", _title, _version))); + if (_background.size()) { + if (_bg_title.empty()) _bg_title = "Background"; - std::cout << "\n " << underline.colorize (_bg_title) << std::endl - << _background << std::endl; + std::cout << "\n " << underline.colorize(_bg_title) << std::endl << _background << std::endl; } - wait_for_enter (); + wait_for_enter(); - std::cout << " " << underline.colorize (format ("What changed in {1}?\n", _version)); - if (_punchline.size ()) - std::cout << footnote.colorize (format ("{1}\n", _punchline)); + std::cout << " " << underline.colorize(format("What changed in {1}?\n", _version)); + if (_punchline.size()) std::cout << footnote.colorize(format("{1}\n", _punchline)); - if (_update.size ()) - std::cout << format ("{1}\n", _update); + if (_update.size()) std::cout << format("{1}\n", _update); - wait_for_enter (); + wait_for_enter(); - if (_reasoning.size ()) { - std::cout << " " << underline.colorize ("What was the motivation behind this feature?\n") + if (_reasoning.size()) { + std::cout << " " << underline.colorize("What was the motivation behind this feature?\n") << _reasoning << std::endl; - wait_for_enter (); + wait_for_enter(); } - if (_actions.size ()) { - std::cout << " " << underline.colorize ("What do I have to do?\n") - << _actions << std::endl; - wait_for_enter (); + if (_actions.size()) { + std::cout << " " << underline.colorize("What do I have to do?\n") << _actions << std::endl; + wait_for_enter(); } } -std::vector NewsItem::all () { +std::vector NewsItem::all() { std::vector items; version2_6_0(items); version3_0_0(items); + version3_1_0(items); + version3_2_0(items); + version3_3_0(items); + version3_4_0(items); return items; } @@ -191,513 +178,445 @@ std::vector NewsItem::all () { // - The .by attribute modifier // - Exporting a report // - Multi-day holidays -void NewsItem::version2_6_0 (std::vector& items) { +void NewsItem::version2_6_0(std::vector& items) { Version version("2.6.0"); ///////////////////////////////////////////////////////////////////////////// - // - Writeable context (major) + // - Writeable context // Detect whether user uses any contexts - auto config = Context::getContext ().config; + auto config = Context::getContext().config; std::stringstream advice; - auto defined = CmdContext::getContexts (); - if (defined.size ()) - { + auto defined = CmdContext::getContexts(); + if (defined.size()) { // Detect the old-style contexts std::vector old_style; - std::copy_if ( - defined.begin(), - defined.end(), - std::back_inserter(old_style), - [&](auto& name){return config.has ("context." + name);} - ); + std::copy_if(defined.begin(), defined.end(), std::back_inserter(old_style), + [&](auto& name) { return config.has("context." + name); }); - if (old_style.size ()) - { - advice << format ( - " You have {1} defined contexts, out of which {2} are old-style:\n", - defined.size (), - std::count_if ( - defined.begin (), - defined.end (), - [&](auto& name){return config.has ("context." + name);} - )); + if (old_style.size()) { + advice << format(" You have {1} defined contexts, out of which {2} are old-style:\n", + defined.size(), + std::count_if(defined.begin(), defined.end(), + [&](auto& name) { return config.has("context." + name); })); - for (auto context: defined) { - std::string old_definition = config.get ("context." + context); - if (old_definition != "") - advice << format (" * {1}: {2}\n", context, old_definition); + for (auto context : defined) { + std::string old_definition = config.get("context." + context); + if (old_definition != "") advice << format(" * {1}: {2}\n", context, old_definition); } advice << "\n" " These need to be migrated to new-style, which uses context..read and\n" " context..write config variables. Please run the following commands:\n"; - for (auto context: defined) { - std::string old_definition = config.get ("context." + context); + for (auto context : defined) { + std::string old_definition = config.get("context." + context); if (old_definition != "") - advice << format (" $ task context define {1} '{2}'\n", context, old_definition); + advice << format(" $ task context define {1} '{2}'\n", context, old_definition); } - advice << "\n" - " Please check these filters are also valid modifications. If a context filter is not\n" - " a valid modification, you can set the context..write configuration variable to\n" - " specify the write context explicitly. Read more in CONTEXT section of man taskrc."; - } - else + advice + << "\n" + " Please check these filters are also valid modifications. If a context filter is " + "not\n" + " a valid modification, you can set the context..write configuration variable " + "to\n" + " specify the write context explicitly. Read more in CONTEXT section of man taskrc."; + } else advice << " You don't have any old-style contexts defined, so you're good to go as is!"; - } - else + } else advice << " You don't have any contexts defined, so you're good to go as is!\n" " Read more about how to use contexts in CONTEXT section of 'man task'."; - NewsItem writeable_context ( - version, - true, - "'Writeable' context", - "Background - what is context?", - " The 'context' is a feature (introduced in 2.5.0) that allows users to apply a\n" - " predefined filter to all task reports.\n" - " \n" - " $ task context define work \"project:Work or +urgent\"\n" - " $ task context work\n" - " Context 'work' set. Use 'task context none' to remove.\n" - " \n" - " Now if we proceed to add two tasks:\n" - " $ task add Talk to Jeff pro:Work\n" - " $ task add Call mom pro:Personal\n" - " \n" - " $ task\n" - " ID Age Project Description Urg\n" - " 1 16s Work Talk to Jeff 1\n" - " \n" - " The task \"Call mom\" will not be listed, because it does not match\n" - " the active context (its project is 'Personal' and not 'Work').", - " The currently active context definition is now applied as default modifications\n" - " when creating new tasks using 'task add' and 'task log'.", - " \n" - " Consider following example, using context 'work' defined as 'project:Work' above:\n" - " \n" - " $ task context work\n" - " $ task add Talk to Jeff\n" - " $ task\n" - " ID Age Project Description Urg \n" - " 1 1s Work Talk to Jeff 1\n" - " ^^^^^^^\n" - " \n" - " Note that project attribute was set to 'Work' automatically.", - " This was a popular feature request. Now, if you have a context active,\n" - " newly added tasks no longer \"fall outside\" of the context by default.", - advice.str () - ); + NewsItem writeable_context( + version, "'Writeable' context", "Background - what is context?", + " The 'context' is a feature (introduced in 2.5.0) that allows users to apply a\n" + " predefined filter to all task reports.\n" + " \n" + " $ task context define work \"project:Work or +urgent\"\n" + " $ task context work\n" + " Context 'work' set. Use 'task context none' to remove.\n" + " \n" + " Now if we proceed to add two tasks:\n" + " $ task add Talk to Jeff pro:Work\n" + " $ task add Call mom pro:Personal\n" + " \n" + " $ task\n" + " ID Age Project Description Urg\n" + " 1 16s Work Talk to Jeff 1\n" + " \n" + " The task \"Call mom\" will not be listed, because it does not match\n" + " the active context (its project is 'Personal' and not 'Work').", + " The currently active context definition is now applied as default modifications\n" + " when creating new tasks using 'task add' and 'task log'.", + " \n" + " Consider following example, using context 'work' defined as 'project:Work' above:\n" + " \n" + " $ task context work\n" + " $ task add Talk to Jeff\n" + " $ task\n" + " ID Age Project Description Urg \n" + " 1 1s Work Talk to Jeff 1\n" + " ^^^^^^^\n" + " \n" + " Note that project attribute was set to 'Work' automatically.", + " This was a popular feature request. Now, if you have a context active,\n" + " newly added tasks no longer \"fall outside\" of the context by default.", + advice.str()); items.push_back(writeable_context); ///////////////////////////////////////////////////////////////////////////// - // - 64-bit datetime support (major) + // - 64-bit datetime support - NewsItem uint64_support ( - version, - false, - "Support for 64-bit timestamps and numeric values", - "", - "", - " Taskwarrior now supports 64-bit timestamps, making it possible to set due dates\n" - " and other date attributes beyond 19 January 2038 (limit of 32-bit timestamps).\n", - " The current limit is 31 December 9999 for display reasons (last 4-digit year).", - " With each year passing by faster than the last, setting tasks for 2040s\n" - " is not as unfeasible as it once was.", - " Don't forget that 50-year anniversary and 'task add' a long-term task today!" - ); + NewsItem uint64_support( + version, "Support for 64-bit timestamps and numeric values", "", "", + " Taskwarrior now supports 64-bit timestamps, making it possible to set due dates\n" + " and other date attributes beyond 19 January 2038 (limit of 32-bit timestamps).\n", + " The current limit is 31 December 9999 for display reasons (last 4-digit year).", + " With each year passing by faster than the last, setting tasks for 2040s\n" + " is not as unfeasible as it once was.", + " Don't forget that 50-year anniversary and 'task add' a long-term task today!"); items.push_back(uint64_support); ///////////////////////////////////////////////////////////////////////////// // - Waiting is a virtual status - NewsItem waiting_status ( - version, - true, - "Deprecation of the status:waiting", - "", - " If a task has a 'wait' attribute set to a date in the future, it is modified\n" - " to have a 'waiting' status. Once that date is no longer in the future, the status\n" - " is modified to back to 'pending'.", - " The 'waiting' value of status is deprecated, instead users should use +WAITING\n" - " virtual tag, or explicitly query for wait.after:now (the two are equivalent).", - " \n" - " The status:waiting query still works in 2.6.0, but support will be dropped in 3.0.", - "", - " In your custom report definitions, the following expressions should be replaced:\n" - " * 'status:pending or status:waiting' should be replaced by 'status:pending'\n" - " * 'status:pending' should be replaced by 'status:pending -WAITING'" - ); + NewsItem waiting_status( + version, "Deprecation of the status:waiting", "", + " If a task has a 'wait' attribute set to a date in the future, it is modified\n" + " to have a 'waiting' status. Once that date is no longer in the future, the status\n" + " is modified to back to 'pending'.", + " The 'waiting' value of status is deprecated, instead users should use +WAITING\n" + " virtual tag, or explicitly query for wait.after:now (the two are equivalent).", + " \n" + " The status:waiting query still works in 2.6.0, but support will be dropped in 3.0.", + "", + " In your custom report definitions, the following expressions should be replaced:\n" + " * 'status:pending or status:waiting' should be replaced by 'status:pending'\n" + " * 'status:pending' should be replaced by 'status:pending -WAITING'"); items.push_back(waiting_status); ///////////////////////////////////////////////////////////////////////////// // - Support for environment variables in the taskrc - NewsItem env_vars ( - version, - true, - "Environment variables in the taskrc", - "", - "", - " Taskwarrior now supports expanding environment variables in the taskrc file,\n" - " allowing users to customize the behaviour of 'task' based on the current env.\n", - " The environment variables can either be used in paths, or as separate values:\n" - " data.location=$XDG_DATA_HOME/task/\n" - " default.project=$PROJECT", - "", - "" - ); + NewsItem env_vars( + version, "Environment variables in the taskrc", "", "", + " Taskwarrior now supports expanding environment variables in the taskrc file,\n" + " allowing users to customize the behaviour of 'task' based on the current env.\n", + " The environment variables can either be used in paths, or as separate values:\n" + " data.location=$XDG_DATA_HOME/task/\n" + " default.project=$PROJECT", + "", ""); items.push_back(env_vars); ///////////////////////////////////////////////////////////////////////////// // - Reports outside of context - NewsItem contextless_reports ( - version, - true, - "Context-less reports", - "", - " By default, every report is affected by currently active context.", - " You can now make a selected report ignore currently active context by setting\n" - " 'report..context' configuration variable to 0.", - "", - " This is useful for users who utilize a single place (such as project:Inbox)\n" - " to collect their new tasks that are then triaged on a regular basis\n" - " (such as in GTD methodology).\n" - " \n" - " In such a case, defining a report that filters for project:Inbox and making it\n" - " fully accessible from any context is a major usability improvement.", - "" - ); + NewsItem contextless_reports( + version, "Context-less reports", "", + " By default, every report is affected by currently active context.", + " You can now make a selected report ignore currently active context by setting\n" + " 'report..context' configuration variable to 0.", + "", + " This is useful for users who utilize a single place (such as project:Inbox)\n" + " to collect their new tasks that are then triaged on a regular basis\n" + " (such as in GTD methodology).\n" + " \n" + " In such a case, defining a report that filters for project:Inbox and making it\n" + " fully accessible from any context is a major usability improvement.", + ""); items.push_back(contextless_reports); ///////////////////////////////////////////////////////////////////////////// // - Exporting a particular report - NewsItem exportable_reports ( - version, - false, - "Exporting a particular report", - "", - "", - " You can now export the tasks listed by a particular report as JSON by simply\n" - " calling 'task export '.\n", - " The export mirrors the filter and the sort order of the report.", - " This feature can be used to quickly process the data displayed in a particular\n" - " report using other CLI tools. For example, the following oneliner\n" - " \n" - " $ task export next | jq '.[].urgency' | datamash mean 1\n" - " 3.3455535142857\n" - " \n" - " combines jq and GNU datamash to compute average urgency of the tasks displayed\n" - " in the 'next' report.", - "" - ); + NewsItem exportable_reports( + version, "Exporting a particular report", "", "", + " You can now export the tasks listed by a particular report as JSON by simply\n" + " calling 'task export '.\n", + " The export mirrors the filter and the sort order of the report.", + " This feature can be used to quickly process the data displayed in a particular\n" + " report using other CLI tools. For example, the following oneliner\n" + " \n" + " $ task export next | jq '.[].urgency' | datamash mean 1\n" + " 3.3455535142857\n" + " \n" + " combines jq and GNU datamash to compute average urgency of the tasks displayed\n" + " in the 'next' report.", + ""); items.push_back(exportable_reports); ///////////////////////////////////////////////////////////////////////////// // - Multi-day holidays - NewsItem multi_holidays ( - version, - false, - "Multi-day holidays", - "", - " Holidays are currently used in 'task calendar' to visualize the workload during\n" - " the upcoming weeks/months. Up to date country-specific holiday data files can be\n" - " obtained from our website, holidata.net.", - " Instead of single-day holiday entries only, Taskwarrior now supports holidays\n" - " that span a range of days (i.e. vacation).\n", - " Use a holiday..start and holiday..end to configure a multi-day holiday:\n" - " \n" - " holiday.sysadmin.name=System Administrator Appreciation Week\n" - " holiday.sysadmin.start=20100730\n" - " holiday.sysadmin.end=20100805", - "", - "" - ); + NewsItem multi_holidays( + version, "Multi-day holidays", "", + " Holidays are currently used in 'task calendar' to visualize the workload during\n" + " the upcoming weeks/months. Up to date country-specific holiday data files can be\n" + " obtained from our website, holidata.net.", + " Instead of single-day holiday entries only, Taskwarrior now supports holidays\n" + " that span a range of days (i.e. vacation).\n", + " Use a holiday..start and holiday..end to configure a multi-day holiday:\n" + " \n" + " holiday.sysadmin.name=System Administrator Appreciation Week\n" + " holiday.sysadmin.start=20100730\n" + " holiday.sysadmin.end=20100805", + "", ""); items.push_back(multi_holidays); ///////////////////////////////////////////////////////////////////////////// // - Unicode 12 - NewsItem unicode_12 ( - version, - false, - "Extended Unicode support (Unicode 12)", - "", - "", - " The support for Unicode character set was improved to support Unicode 12.\n" - " This means better support for various language-specific characters - and emojis!", - "", - " Extended unicode support for language-specific characters helps non-English speakers.\n" - " While most users don't enter emojis as task metadata, automated task creation tools,\n" - " such as bugwarrior, might create tasks with exotic Unicode data.", - " You can try it out - 'task add Prepare for an 👽 invasion!'" - ); + NewsItem unicode_12( + version, "Extended Unicode support (Unicode 12)", "", "", + " The support for Unicode character set was improved to support Unicode 12.\n" + " This means better support for various language-specific characters - and emojis!", + "", + " Extended unicode support for language-specific characters helps non-English speakers.\n" + " While most users don't enter emojis as task metadata, automated task creation tools,\n" + " such as bugwarrior, might create tasks with exotic Unicode data.", + " You can try it out - 'task add Prepare for an 👽 invasion!'"); items.push_back(unicode_12); ///////////////////////////////////////////////////////////////////////////// // - The .by attribute modifier - NewsItem by_modifier ( - version, - false, - "The .by attribute modifier", - "", - "", - " A new attribute modifier '.by' was introduced, equivalent to the operator '<='.\n", - " This modifier can be used to list all tasks due by the end of the months,\n" - " including the last day of the month, using: 'due.by:eom' query", - " There was no convenient way to express '<=' relation using attribute modifiers.\n" - " As a workaround, instead of 'due.by:eom' one could use 'due.before:eom+1d',\n" - " but that requires a certain amount of mental overhead.", - "" - ); + NewsItem by_modifier( + version, "The .by attribute modifier", "", "", + " A new attribute modifier '.by' was introduced, equivalent to the operator '<='.\n", + " This modifier can be used to list all tasks due by the end of the months,\n" + " including the last day of the month, using: 'due.by:eom' query", + " There was no convenient way to express '<=' relation using attribute modifiers.\n" + " As a workaround, instead of 'due.by:eom' one could use 'due.before:eom+1d',\n" + " but that requires a certain amount of mental overhead.", + ""); items.push_back(by_modifier); ///////////////////////////////////////////////////////////////////////////// // - Context-specific configuration overrides - NewsItem context_config ( - version, - false, - "Context-specific configuration overrides", - "", - "", - " Any context can now define context-specific configuration overrides\n" - " via context..rc.=.\n", - " This allows the user to customize the behaviour of Taskwarrior in a given context,\n" - " for example, to change the default command in the 'work' context to 'overdue':\n" - "\n" - " $ task config context.work.rc.default.command overdue\n" - "\n" - " Another example would be to ensure that while context 'work' is active, tasks get\n" - " stored in a ~/.worktasks directory:\n" - "\n" - " $ task config context.work.rc.data.location=~/.worktasks", - "", - "" - ); + NewsItem context_config( + version, "Context-specific configuration overrides", "", "", + " Any context can now define context-specific configuration overrides\n" + " via context..rc.=.\n", + " This allows the user to customize the behaviour of Taskwarrior in a given context,\n" + " for example, to change the default command in the 'work' context to 'overdue':\n" + "\n" + " $ task config context.work.rc.default.command overdue\n" + "\n" + " Another example would be to ensure that while context 'work' is active, tasks get\n" + " stored in a ~/.worktasks directory:\n" + "\n" + " $ task config context.work.rc.data.location=~/.worktasks", + "", ""); items.push_back(context_config); ///////////////////////////////////////////////////////////////////////////// // - XDG config home support - NewsItem xdg_support ( - version, - true, - "Support for XDG Base Directory Specification", - "", - " The XDG Base Directory specification provides standard locations to store\n" - " application data, configuration, state, and cached data in order to keep $HOME\n" - " clutter-free. The locations are usually set to ~/.local/share, ~/.config,\n" - " ~/.local/state and ~/.cache respectively.", - " If taskrc is not found at '~/.taskrc', Taskwarrior will attempt to find it\n" - " at '$XDG_CONFIG_HOME/task/taskrc' (defaults to '~/.config/task/taskrc').", - "", - " This allows users to fully follow XDG Base Directory Spec by moving their taskrc:\n" - " $ mkdir $XDG_CONFIG_HOME/task\n" - " $ mv ~/.taskrc $XDG_CONFIG_HOME/task/taskrc\n\n" - " and further setting:\n" - " data.location=$XDG_DATA_HOME/task/\n" - " hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n" - " Solutions in the past required symlinks or more cumbersome configuration overrides.", - " If you configure your data.location and hooks.location as above, ensure\n" - " that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n" - " otherwise they're going to expand to empty string. Alternatively you can\n" - " hardcode the desired paths on your system." - ); + NewsItem xdg_support( + version, "Support for XDG Base Directory Specification", "", + " The XDG Base Directory specification provides standard locations to store\n" + " application data, configuration, state, and cached data in order to keep $HOME\n" + " clutter-free. The locations are usually set to ~/.local/share, ~/.config,\n" + " ~/.local/state and ~/.cache respectively.", + " If taskrc is not found at '~/.taskrc', Taskwarrior will attempt to find it\n" + " at '$XDG_CONFIG_HOME/task/taskrc' (defaults to '~/.config/task/taskrc').", + "", + " This allows users to fully follow XDG Base Directory Spec by moving their taskrc:\n" + " $ mkdir $XDG_CONFIG_HOME/task\n" + " $ mv ~/.taskrc $XDG_CONFIG_HOME/task/taskrc\n\n" + " and further setting:\n" + " data.location=$XDG_DATA_HOME/task/\n" + " hooks.location=$XDG_CONFIG_HOME/task/hooks/\n\n" + " Solutions in the past required symlinks or more cumbersome configuration overrides.", + " If you configure your data.location and hooks.location as above, ensure\n" + " that the XDG_DATA_HOME and XDG_CONFIG_HOME environment variables are set,\n" + " otherwise they're going to expand to empty string. Alternatively you can\n" + " hardcode the desired paths on your system."); items.push_back(xdg_support); ///////////////////////////////////////////////////////////////////////////// // - Update holiday data - NewsItem holidata_2022 ( - version, - false, - "Updated holiday data for 2022", - "", - "", - " Holiday data has been refreshed for 2022 and five more holiday locales\n" - " have been added: fr-CA, hu-HU, pt-BR, sk-SK and sv-FI.", - "", - " Refreshing the holiday data is part of every release. The addition of the new\n" - " locales allows us to better support users in those particular countries." - ); + NewsItem holidata_2022( + version, "Updated holiday data for 2022", "", "", + " Holiday data has been refreshed for 2022 and five more holiday locales\n" + " have been added: fr-CA, hu-HU, pt-BR, sk-SK and sv-FI.", + "", + " Refreshing the holiday data is part of every release. The addition of the new\n" + " locales allows us to better support users in those particular countries."); items.push_back(holidata_2022); } -void NewsItem::version3_0_0 (std::vector& items) { +void NewsItem::version3_0_0(std::vector& items) { Version version("3.0.0"); - NewsItem sync { - version, - /*major=*/true, - /*title=*/"New data model and sync backend", - /*bg_title=*/"", - /*background=*/"", - /*punchline=*/ - "The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n" - "supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n" - "although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n" - "For details. As part of this change, the on-disk storage format has also changed.\n", - /*update=*/ - "This is a breaking upgrade: you must export your task database from 2.x and re-import\n" - "it into 3.x. Hooks run during task import, so if you have any hooks defined,\n" - "temporarily disable them for this operation.\n\n" - "See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior 3.0.", + NewsItem sync{ + version, + /*title=*/"New data model and sync backend", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/ + "The sync functionality for Taskwarrior has been rewritten entirely, and no longer\n" + "supports taskserver/taskd. The most robust solution is a cloud-storage backend,\n" + "although a less-mature taskchampion-sync-server is also available. See `task-sync(5)`\n" + "For details. As part of this change, the on-disk storage format has also changed.\n", + /*update=*/ + "This is a breaking upgrade: you must export your task database from 2.x and re-import\n" + "it into 3.x. Hooks run during task import, so if you have any hooks defined,\n" + "temporarily disable them for this operation.\n\n" + "See https://taskwarrior.org/docs/upgrade-3/ for information on upgrading to Taskwarrior " + "3.0.", }; items.push_back(sync); } +void NewsItem::version3_1_0(std::vector& items) { + Version version("3.1.0"); + NewsItem purge{ + version, + /*title=*/"Purging Tasks, Manually or Automatically", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/ + "Support for `task purge` has been restored, and new support added for automatically\n" + "expiring old tasks.\n\n", + /*update=*/ + "The `task purge` command removes tasks entirely, in contrast to `task delete` which merely\n" + "sets the task status to 'Deleted'. This functionality existed in versions 2.x but was\n" + "temporarily removed in 3.0.\n\n" + "The new `purge.on-sync` configuration parameter controls automatic purging of old tasks.\n" + "An old task is one with status 'Deleted' that has not been modified in 180 days. This\n" + "functionality is optional and not enabled by default."}; + items.push_back(purge); + NewsItem news{ + version, + /*title=*/"Improved 'task news'", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/ + "The news you are reading now is improved.\n\n", + /*update=*/ + "The `task news` command now always shows all new information, not just 'major' news,\n" + "and will only show that news once. New installs will assume all news has been read.\n" + "Finally, news can be completely hidden by removing 'news' from the 'verbose' config."}; + items.push_back(news); +} + +void NewsItem::version3_2_0(std::vector& items) { + Version version("3.2.0"); + NewsItem info{ + version, + /*title=*/"`task info` Journal Restored", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/"", + /*update=*/ + "Support for the \"journal\" output in `task info` has been restored. The command now\n" + "displays a list of changes made to the task, with timestamps.\n\n"}; + items.push_back(info); +} + +void NewsItem::version3_3_0(std::vector& items) { + Version version("3.3.0"); + NewsItem info{ + version, + /*title=*/"AWS S3 Sync", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/"Use an AWS S3 bucket to sync Taskwarrior", + /*update=*/ + "Taskwarrior now supports AWS as a backend for sync, in addition to existing support\n" + "for GCP and taskchampion-sync-server. See `man task-sync` for details.\n\n"}; + items.push_back(info); +} + +void NewsItem::version3_4_0(std::vector& items) { + Version version("3.4.0"); + NewsItem info{version, + /*title=*/"Read-Only Access", + /*bg_title=*/"", + /*background=*/"", + /*punchline=*/"Some Taskwarrior commands operate faster in read-only mode", + /*update=*/ + "Some commands do not need to write to the DB, so can open it in read-only\n" + "mode and thus more quickly. This does not include reports (task lists),\n" + "unless the `gc` config is false. Use `rc.gc=0` in command-lines to allow\n" + "read-only access.\n\n"}; + items.push_back(info); +} + //////////////////////////////////////////////////////////////////////////////// -int CmdNews::execute (std::string& output) -{ - auto words = Context::getContext ().cli2.getWords (); - auto config = Context::getContext ().config; +int CmdNews::execute(std::string& output) { + auto words = Context::getContext().cli2.getWords(); + auto config = Context::getContext().config; // Supress compiler warning about unused argument output = ""; std::vector items = NewsItem::all(); - Version news_version(Context::getContext ().config.get ("news.version")); + Version news_version(Context::getContext().config.get("news.version")); Version current_version = Version::Current(); // 2.6.0 is the earliest version with news support. - if (!news_version.is_valid()) - news_version = Version("2.6.0"); + if (!news_version.is_valid()) news_version = Version("2.6.0"); - bool full_summary = false; - bool major_items = true; - - for (auto word: words) - { - if (word == "major") { - major_items = true; - break; - } - - if (word == "minor") { - major_items = false; - break; - } - - if (word == "all") { - full_summary = true; - break; - } - } - - signal (SIGINT, signal_handler); + signal(SIGINT, signal_handler); // Remove items that have already been shown - items.erase ( - std::remove_if (items.begin (), items.end (), [&](const NewsItem& n){return n._version <= news_version;}), - items.end () - ); + items.erase(std::remove_if(items.begin(), items.end(), + [&](const NewsItem& n) { return n._version <= news_version; }), + items.end()); - // Remove non-major items if displaying a non-full (abbreviated) summary - int total_highlights = items.size (); - if (! full_summary) - items.erase ( - std::remove_if (items.begin (), items.end (), [&](const NewsItem& n){return n._major != major_items;}), - items.end () - ); - - Color bold = Color ("bold"); - if (items.empty ()) { - std::cout << bold.colorize ("You are up to date!\n"); + Color bold = Color("bold"); + if (items.empty()) { + std::cout << bold.colorize("You are up to date!\n"); } else { // Print release notes - std::cout << bold.colorize (format ( - "\n" - "================================================\n" - "Taskwarrior {1} through {2} Release Highlights\n" - "================================================\n", - news_version, - current_version)); + std::cout << bold.colorize( + format("\n" + "================================================\n" + "Taskwarrior {1} through {2} Release Highlights\n" + "================================================\n", + news_version, current_version)); - for (unsigned short i=0; i < items.size (); i++) { - std::cout << format ("\n({1}/{2}) ", i+1, items.size ()); - items[i].render (); + for (unsigned short i = 0; i < items.size(); i++) { + std::cout << format("\n({1}/{2}) ", i + 1, items.size()); + items[i].render(); } std::cout << "Thank you for catching up on the new features!\n"; } - wait_for_enter (); - - // Display outro - Datetime now; - Datetime beginning (2006, 11, 29); - Duration development_time = Duration (now - beginning); - - Color underline = Color ("underline"); - - std::stringstream outro; - outro << underline.colorize (bold.colorize ("Taskwarrior crowdfunding\n")); - outro << format ( - "Taskwarrior has been in development for {1} years but its survival\n" - "depends on your support!\n\n" - "Please consider joining our {2} fundraiser to help us fund maintenance\n" - "and development of new features:\n\n", - std::lround (static_cast(development_time.days ()) / 365.25), - now.year () - ); - outro << bold.colorize(" https://github.com/sponsors/GothenburgBitFactory/\n\n"); - outro << "Perks are available for our sponsors.\n"; - - std::cout << outro.str (); + wait_for_enter(); // Set a mark in the config to remember which version's release notes were displayed - if (full_summary && news_version != current_version) - { - CmdConfig::setConfigVariable ("news.version", std::string(current_version), false); - - // Revert back to default signal handling after displaying the outro - signal (SIGINT, SIG_DFL); - - std::string question = format ( - "\nWould you like to open Taskwarrior {1} fundraising campaign to read more?", - now.year () - ); - - std::vector options {"yes", "no"}; - std::vector matches; - - std::cout << question << " (YES/no) "; - - std::string answer; - std::getline (std::cin, answer); - - if (std::cin.eof () || trim (answer).empty ()) - answer = "yes"; - else - lowerCase (trim (answer)); - - autoComplete (answer, options, matches, 1); // Hard-coded 1. - - if (matches.size () == 1 && matches[0] == "yes") -#if defined (DARWIN) - system ("open 'https://github.com/sponsors/GothenburgBitFactory/'"); -#else - system ("xdg-open 'https://github.com/sponsors/GothenburgBitFactory/'"); -#endif - - std::cout << std::endl; - } - else - wait_for_enter (); // Do not display the outro and footnote at once - - if (! items.empty() && ! full_summary && major_items) { - Context::getContext ().footnote (format ( - "Only major highlights were displayed ({1} out of {2} total).\n" - "If you're interested in more release highlights, run 'task news {3} minor'.", - items.size (), - total_highlights, - current_version - )); + if (news_version < current_version) { + CmdConfig::setConfigVariable("news.version", std::string(current_version), false); } return 0; } + +bool CmdNews::should_nag() { + if (!Context::getContext().verbose("news")) { + return false; + } + + Version news_version(Context::getContext().config.get("news.version")); + if (!news_version.is_valid()) news_version = Version("2.6.0"); + + Version current_version = Version::Current(); + + if (news_version >= current_version) { + return false; + } + + // Check if there are actually any interesting news items to show. + std::vector items = NewsItem::all(); + for (auto& item : items) { + if (item._version > news_version) { + return true; + } + } + + return false; +} diff --git a/src/commands/CmdNews.h b/src/commands/CmdNews.h index a74b2dda3..294c0f862 100644 --- a/src/commands/CmdNews.h +++ b/src/commands/CmdNews.h @@ -27,16 +27,16 @@ #ifndef INCLUDED_CMDNEWS #define INCLUDED_CMDNEWS -#include -#include #include #include +#include #include +#include + class NewsItem { -public: + public: Version _version; - bool _major = false; std::string _title; std::string _bg_title; std::string _background; @@ -45,31 +45,28 @@ public: std::string _reasoning; std::string _actions; - void render (); + void render(); static std::vector all(); - static void version2_6_0 (std::vector&); - static void version3_0_0 (std::vector&); + static void version2_6_0(std::vector&); + static void version3_0_0(std::vector&); + static void version3_1_0(std::vector&); + static void version3_2_0(std::vector&); + static void version3_3_0(std::vector&); + static void version3_4_0(std::vector&); -private: - NewsItem ( - Version, - bool, - const std::string&, - const std::string& = "", - const std::string& = "", - const std::string& = "", - const std::string& = "", - const std::string& = "", - const std::string& = "" - ); + private: + NewsItem(Version, const std::string&, const std::string& = "", const std::string& = "", + const std::string& = "", const std::string& = "", const std::string& = "", + const std::string& = ""); }; -class CmdNews : public Command -{ -public: - CmdNews (); - int execute (std::string&); +class CmdNews : public Command { + public: + CmdNews(); + int execute(std::string&); + + static bool should_nag(); }; #endif diff --git a/src/commands/CmdPrepend.cpp b/src/commands/CmdPrepend.cpp index 0e59d5a30..afdf73025 100644 --- a/src/commands/CmdPrepend.cpp +++ b/src/commands/CmdPrepend.cpp @@ -25,112 +25,104 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include #include -#include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdPrepend::CmdPrepend () -{ - _keyword = "prepend"; - _usage = "task prepend "; - _description = "Prepends text to an existing task description"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdPrepend::CmdPrepend() { + _keyword = "prepend"; + _usage = "task prepend "; + _description = "Prepends text to an existing task description"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdPrepend::execute (std::string&) -{ +int CmdPrepend::execute(std::string&) { int rc = 0; int count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // TODO Complain when no modifications are specified. // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - Task before (task); + for (auto& task : filtered) { + Task before(task); // Prepend to the specified task. - std::string question = format ("Prepend to task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + std::string question = + format("Prepend to task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modPrepend, true); + task.modify(Task::modPrepend, true); - if (permission (before.diff (task) + question, filtered.size ())) - { - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Prepending to task {1} '{2}'.", task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); + feedback_affected("Prepending to task {1} '{2}'.", task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); // Prepend to siblings. - if (task.has ("parent")) - { - if ((Context::getContext ().config.get ("recurrence.confirmation") == "prompt" - && confirm ("This is a recurring task. Do you want to prepend to all pending recurrences of this same task?")) || - Context::getContext ().config.getBoolean ("recurrence.confirmation")) - { - std::vector siblings = Context::getContext ().tdb2.siblings (task); - for (auto& sibling : siblings) - { - sibling.modify (Task::modPrepend, true); - Context::getContext ().tdb2.modify (sibling); + if (task.has("parent")) { + if ((Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm("This is a recurring task. Do you want to prepend to all pending recurrences " + "of this same task?")) || + Context::getContext().config.getBoolean("recurrence.confirmation")) { + std::vector siblings = Context::getContext().tdb2.siblings(task); + for (auto& sibling : siblings) { + sibling.modify(Task::modPrepend, true); + Context::getContext().tdb2.modify(sibling); ++count; - feedback_affected ("Prepending to recurring task {1} '{2}'.", sibling); + feedback_affected("Prepending to recurring task {1} '{2}'.", sibling); } // Prepend to the parent Task parent; - Context::getContext ().tdb2.get (task.get ("parent"), parent); - parent.modify (Task::modPrepend, true); - Context::getContext ().tdb2.modify (parent); + Context::getContext().tdb2.get(task.get("parent"), parent); + parent.modify(Task::modPrepend, true); + Context::getContext().tdb2.modify(parent); } } - } - else - { + } else { std::cout << "Task not prepended.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } } // Now list the project changes. for (auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Prepended {1} task." : "Prepended {1} tasks.", count); + feedback_affected(count == 1 ? "Prepended {1} task." : "Prepended {1} tasks.", count); return rc; } diff --git a/src/commands/CmdPrepend.h b/src/commands/CmdPrepend.h index 2e07a6253..4fb499f96 100644 --- a/src/commands/CmdPrepend.h +++ b/src/commands/CmdPrepend.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDPREPEND #define INCLUDED_CMDPREPEND -#include #include -class CmdPrepend : public Command -{ -public: - CmdPrepend (); - int execute (std::string&); +#include + +class CmdPrepend : public Command { + public: + CmdPrepend(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdProjects.cpp b/src/commands/CmdProjects.cpp index 2c5dea7d4..a74308757 100644 --- a/src/commands/CmdProjects.cpp +++ b/src/commands/CmdProjects.cpp @@ -25,173 +25,148 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include #include #include #include +#include #include -#include + #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdProjects::CmdProjects () -{ - _keyword = "projects"; - _usage = "task projects"; - _description = "Shows all project names used"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdProjects::CmdProjects() { + _keyword = "projects"; + _usage = "task projects"; + _description = "Shows all project names used"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdProjects::execute (std::string& output) -{ +int CmdProjects::execute(std::string& output) { int rc = 0; // Get all the tasks. - handleUntil (); - handleRecurrence (); - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); - if (Context::getContext ().config.getBoolean ("list.all.projects")) - for (auto& task : Context::getContext ().tdb2.completed_tasks ()) - tasks.push_back (task); + if (Context::getContext().config.getBoolean("list.all.projects")) + for (auto& task : Context::getContext().tdb2.completed_tasks()) tasks.push_back(task); // Apply the filter. Filter filter; - std::vector filtered; - filter.subset (tasks, filtered); - int quantity = filtered.size (); + std::vector filtered; + filter.subset(tasks, filtered); + int quantity = filtered.size(); std::stringstream out; // Scan all the tasks for their project name, building a map using project // names as keys. - std::map unique; + std::map unique; bool no_project = false; std::string project; - for (auto& task : filtered) - { - if (task.getStatus () == Task::deleted) - { + for (auto& task : filtered) { + if (task.getStatus() == Task::deleted) { --quantity; continue; } // Increase the count for the project the task belongs to and all // its super-projects - project = task.get ("project"); + project = task.get("project"); - std::vector projects = extractParents (project); - projects.push_back (project); + std::vector projects = extractParents(project); + projects.push_back(project); - for (auto& parent : projects) - unique[parent] += 1; + for (auto& parent : projects) unique[parent] += 1; - if (project == "") - no_project = true; + if (project == "") no_project = true; } - if (unique.size ()) - { + if (unique.size()) { // Render a list of project names from the map. Table view; - view.width (Context::getContext ().getWidth ()); - view.add ("Project"); - view.add ("Tasks", false); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.add("Project"); + view.add("Tasks", false); + setHeaderUnderline(view); // create sorted list of table entries - std::list > sorted_view; - sort_projects (sorted_view, unique); + std::list> sorted_view; + sort_projects(sorted_view, unique); // construct view from sorted list - for (auto& item: sorted_view) - { - int row = view.addRow (); - view.set (row, 0, (item.first == "" - ? "(none)" - : indentProject (item.first, " ", '.'))); - view.set (row, 1, item.second); + for (auto& item : sorted_view) { + int row = view.addRow(); + view.set(row, 0, (item.first == "" ? "(none)" : indentProject(item.first, " ", '.'))); + view.set(row, 1, item.second); } - int number_projects = unique.size (); - if (no_project) - --number_projects; + int number_projects = unique.size(); + if (no_project) --number_projects; - out << optionalBlankLine () - << view.render () - << optionalBlankLine () - << (number_projects == 1 - ? format ("{1} project", number_projects) - : format ("{1} projects", number_projects)) - << ' ' - << (quantity == 1 - ? format ("({1} task)", quantity) - : format ("({1} tasks)", quantity)) + out << optionalBlankLine() << view.render() << optionalBlankLine() + << (number_projects == 1 ? format("{1} project", number_projects) + : format("{1} projects", number_projects)) + << ' ' << (quantity == 1 ? format("({1} task)", quantity) : format("({1} tasks)", quantity)) << '\n'; - } - else - { + } else { out << "No projects.\n"; rc = 1; } - output = out.str (); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionProjects::CmdCompletionProjects () -{ - _keyword = "_projects"; - _usage = "task _projects"; - _description = "Shows only a list of all project names used"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdCompletionProjects::CmdCompletionProjects() { + _keyword = "_projects"; + _usage = "task _projects"; + _description = "Shows only a list of all project names used"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionProjects::execute (std::string& output) -{ +int CmdCompletionProjects::execute(std::string& output) { // Get all the tasks. - handleUntil (); - handleRecurrence (); - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); - if (Context::getContext ().config.getBoolean ("list.all.projects")) - for (auto& task : Context::getContext ().tdb2.completed_tasks ()) - tasks.push_back (task); + if (Context::getContext().config.getBoolean("list.all.projects")) + for (auto& task : Context::getContext().tdb2.completed_tasks()) tasks.push_back(task); // Apply the filter. Filter filter; - std::vector filtered; - filter.subset (tasks, filtered); + std::vector filtered; + filter.subset(tasks, filtered); // Scan all the tasks for their project name, building a map using project // names as keys. - std::map unique; - for (auto& task : filtered) - unique[task.get ("project")] = 0; + std::map unique; + for (auto& task : filtered) unique[task.get("project")] = 0; for (auto& project : unique) - if (project.first.length ()) - output += project.first + '\n'; + if (project.first.length()) output += project.first + '\n'; return 0; } diff --git a/src/commands/CmdProjects.h b/src/commands/CmdProjects.h index 206d3a664..180db8f8b 100644 --- a/src/commands/CmdProjects.h +++ b/src/commands/CmdProjects.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDPROJECTS #define INCLUDED_CMDPROJECTS -#include #include -class CmdProjects : public Command -{ -public: - CmdProjects (); - int execute (std::string&); +#include + +class CmdProjects : public Command { + public: + CmdProjects(); + int execute(std::string&); }; -class CmdCompletionProjects : public Command -{ -public: - CmdCompletionProjects (); - int execute (std::string&); +class CmdCompletionProjects : public Command { + public: + CmdCompletionProjects(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdPurge.cpp b/src/commands/CmdPurge.cpp index ffde6543e..d5b3c89fe 100644 --- a/src/commands/CmdPurge.cpp +++ b/src/commands/CmdPurge.cpp @@ -25,32 +25,145 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include +#include +#include +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdPurge::CmdPurge () -{ - _keyword = "purge"; - _usage = "task purge"; - _description = "(deprecated; does nothing)"; - _read_only = false; - _displays_id = false; - _needs_confirm = true; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdPurge::CmdPurge() { + _keyword = "purge"; + _usage = "task purge"; + _description = "Removes the specified tasks from the data files. Causes permanent loss of data."; + _read_only = false; + _displays_id = false; + _needs_confirm = true; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdPurge::execute (std::string&) -{ - Context::getContext ().footnote ("As of version 3.0, this command has no effect."); - Context::getContext ().footnote ("Deleted tasks are removed from the task list automatically after they expire."); - return 0; +// Purges the task, while taking care of: +// - dependencies on this task +// - child tasks +void CmdPurge::handleRelations(Task& task, std::vector& tasks) { + handleDeps(task); + handleChildren(task, tasks); + tasks.push_back(task); +} + +//////////////////////////////////////////////////////////////////////////////// +// Makes sure that any task having the dependency on the task being purged +// has that dependency removed, to preserve referential integrity. +void CmdPurge::handleDeps(Task& task) { + std::string uuid = task.get("uuid"); + + for (auto& blockedConst : Context::getContext().tdb2.all_tasks()) { + Task& blocked = const_cast(blockedConst); + if (blocked.hasDependency(uuid)) { + blocked.removeDependency(uuid); + Context::getContext().tdb2.modify(blocked); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Makes sure that with any recurrence parent are all the child tasks removed +// as well. If user chooses not to, the whole command is aborted. +void CmdPurge::handleChildren(Task& task, std::vector& tasks) { + // If this is not a recurrence parent, we have no job here + if (!task.has("mask")) return; + + std::string uuid = task.get("uuid"); + std::vector children; + + // Find all child tasks + for (auto& childConst : Context::getContext().tdb2.all_tasks()) { + Task& child = const_cast(childConst); + + if (child.get("parent") == uuid) { + if (child.getStatus() != Task::deleted) + // In case any child task is not deleted, bail out + throw format( + "Task '{1}' is a recurrence template. Its child task {2} must be deleted before it can " + "be purged.", + task.get("description"), child.identifier(true)); + else + children.push_back(child); + } + } + + // If there are no children, our job is done + if (children.empty()) return; + + // Ask for confirmation to purge them, if needed + std::string question = format( + "Task '{1}' is a recurrence template. All its {2} deleted children tasks will be purged as " + "well. Continue?", + task.get("description"), children.size()); + + if (Context::getContext().config.getBoolean("recurrence.confirmation") || + (Context::getContext().config.get("recurrence.confirmation") == "prompt" && + confirm(question))) { + for (auto& child : children) handleRelations(child, tasks); + } else + throw std::string("Purge operation aborted."); +} + +//////////////////////////////////////////////////////////////////////////////// +int CmdPurge::execute(std::string&) { + int rc = 0; + std::vector tasks; + bool matched_deleted = false; + + Filter filter; + std::vector filtered; + + // Apply filter. + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); + return 1; + } + + for (auto& task : filtered) { + // Allow purging of deleted tasks only. Hence no need to deal with: + // - unblocked tasks notifications (deleted tasks are not blocking) + // - project changes (deleted tasks not included in progress) + // It also has the nice property of being explicit - users need to + // mark tasks as deleted before purging. + if (task.getStatus() == Task::deleted) { + // Mark that at least one deleted task matched the filter + matched_deleted = true; + + std::string question; + question = format("Permanently remove task {1} '{2}'?", task.identifier(true), + task.get("description")); + + if (permission(question, filtered.size())) handleRelations(task, tasks); + } + } + + // Now that any exceptions are handled, actually purge the tasks. + for (auto& task : tasks) { + Context::getContext().tdb2.purge(task); + } + + if (filtered.size() > 0 and !matched_deleted) + Context::getContext().footnote( + "No deleted tasks specified. Maybe you forgot to delete tasks first?"); + + feedback_affected(tasks.size() == 1 ? "Purged {1} task." : "Purged {1} tasks.", tasks.size()); + return rc; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdPurge.h b/src/commands/CmdPurge.h index 96db2977d..f46137745 100644 --- a/src/commands/CmdPurge.h +++ b/src/commands/CmdPurge.h @@ -27,14 +27,20 @@ #ifndef INCLUDED_CMDPURGE #define INCLUDED_CMDPURGE -#include #include -class CmdPurge : public Command -{ -public: - CmdPurge (); - int execute (std::string&); +#include +#include + +class CmdPurge : public Command { + private: + void handleRelations(Task& task, std::vector& tasks); + void handleChildren(Task& task, std::vector& tasks); + void handleDeps(Task& task); + + public: + CmdPurge(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdReports.cpp b/src/commands/CmdReports.cpp index c881f38d0..4aa65e712 100644 --- a/src/commands/CmdReports.cpp +++ b/src/commands/CmdReports.cpp @@ -25,82 +25,78 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include #include #include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdReports::CmdReports () -{ - _keyword = "reports"; - _usage = "task reports"; - _description = "Lists all supported reports"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdReports::CmdReports() { + _keyword = "reports"; + _usage = "task reports"; + _description = "Lists all supported reports"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::config; + _category = Command::Category::config; } //////////////////////////////////////////////////////////////////////////////// -int CmdReports::execute (std::string& output) -{ - std::vector reports; +int CmdReports::execute(std::string& output) { + std::vector reports; // Add custom reports. - for (auto& i : Context::getContext ().config) - { - if (i.first.substr (0, 7) == "report.") - { - std::string report = i.first.substr (7); - auto columns = report.find (".columns"); - if (columns != std::string::npos) - reports.push_back (report.substr (0, columns)); + for (auto& i : Context::getContext().config) { + if (i.first.substr(0, 7) == "report.") { + std::string report = i.first.substr(7); + auto columns = report.find(".columns"); + if (columns != std::string::npos) reports.push_back(report.substr(0, columns)); } } // Add known reports. - reports.push_back ("burndown.daily"); - reports.push_back ("burndown.monthly"); - reports.push_back ("burndown.weekly"); - reports.push_back ("ghistory.annual"); - reports.push_back ("ghistory.monthly"); - reports.push_back ("history.annual"); - reports.push_back ("history.monthly"); - reports.push_back ("information"); - reports.push_back ("projects"); - reports.push_back ("summary"); - reports.push_back ("tags"); + reports.push_back("burndown.daily"); + reports.push_back("burndown.monthly"); + reports.push_back("burndown.weekly"); + reports.push_back("ghistory.annual"); + reports.push_back("ghistory.monthly"); + reports.push_back("history.annual"); + reports.push_back("history.monthly"); + reports.push_back("information"); + reports.push_back("projects"); + reports.push_back("summary"); + reports.push_back("tags"); - std::sort (reports.begin (), reports.end ()); + std::sort(reports.begin(), reports.end()); // Compose the output. std::stringstream out; Table view; - view.width (Context::getContext ().getWidth ()); - view.add ("Report"); - view.add ("Description"); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.add("Report"); + view.add("Description"); + setHeaderUnderline(view); - for (auto& report : reports) - { - int row = view.addRow (); - view.set (row, 0, report); - view.set (row, 1, Context::getContext ().commands[report]->description ()); + for (auto& report : reports) { + int row = view.addRow(); + view.set(row, 0, report); + view.set(row, 1, Context::getContext().commands[report]->description()); } - out << optionalBlankLine () - << view.render () - << optionalBlankLine () - << format ("{1} reports\n", reports.size ()); + out << optionalBlankLine() << view.render() << optionalBlankLine() + << format("{1} reports\n", reports.size()); - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdReports.h b/src/commands/CmdReports.h index 0dc74e4fc..893558d3a 100644 --- a/src/commands/CmdReports.h +++ b/src/commands/CmdReports.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDREPORTS #define INCLUDED_CMDREPORTS -#include #include -class CmdReports : public Command -{ -public: - CmdReports (); - int execute (std::string&); +#include + +class CmdReports : public Command { + public: + CmdReports(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdShow.cpp b/src/commands/CmdShow.cpp index 27e0af744..7d839e060 100644 --- a/src/commands/CmdShow.cpp +++ b/src/commands/CmdShow.cpp @@ -25,256 +25,254 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include -#include #include #include #include +#include +#include #include +#include +#include +#include + #define STRING_CMD_SHOW_DIFFER_COLOR "These are highlighted in {1} above." -#define STRING_CMD_SHOW_CONFIG_ERROR "Configuration error: {1} contains an unrecognized value '{2}'." +#define STRING_CMD_SHOW_CONFIG_ERROR \ + "Configuration error: {1} contains an unrecognized value '{2}'." extern std::string configurationDefaults; //////////////////////////////////////////////////////////////////////////////// -CmdShow::CmdShow () -{ - _keyword = "show"; - _usage = "task show [all | substring]"; - _description = "Shows all configuration variables or subset"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdShow::CmdShow() { + _keyword = "show"; + _usage = "task show [all | substring]"; + _description = "Shows all configuration variables or subset"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::config; + _category = Command::Category::config; } //////////////////////////////////////////////////////////////////////////////// -int CmdShow::execute (std::string& output) -{ +int CmdShow::execute(std::string& output) { int rc = 0; std::stringstream out; // Obtain the arguments from the description. That way, things like '--' // have already been handled. - std::vector words = Context::getContext ().cli2.getWords (); - if (words.size () > 1) - throw std::string ("You can only specify 'all' or a search string."); + std::vector words = Context::getContext().cli2.getWords(); + if (words.size() > 1) throw std::string("You can only specify 'all' or a search string."); - int width = Context::getContext ().getWidth (); + int width = Context::getContext().getWidth(); // Complain about configuration variables that are not recognized. // These are the regular configuration variables. // Note that there is a leading and trailing space, to make it easier to // search for whole words. std::string recognized = - " abbreviation.minimum" - " active.indicator" - " allow.empty.filter" - " avoidlastcolumn" - " bulk" - " calendar.details" - " calendar.details.report" - " calendar.holidays" - " calendar.legend" - " calendar.monthsperline" - " calendar.offset" - " calendar.offset.value" - " color" - " color.active" - " color.alternate" - " color.blocked" - " color.blocking" - " color.burndown.done" - " color.burndown.pending" - " color.burndown.started" - " color.calendar.due" - " color.calendar.due.today" - " color.calendar.holiday" - " color.calendar.scheduled" - " color.calendar.overdue" - " color.calendar.today" - " color.calendar.weekend" - " color.calendar.weeknumber" - " color.completed" - " color.debug" - " color.deleted" - " color.due" - " color.due.today" - " color.warning" - " color.error" - " color.footnote" - " color.header" - " color.history.add" - " color.history.delete" - " color.history.done" - " color.label" - " color.label.sort" - " color.overdue" - " color.recurring" - " color.scheduled" - " color.summary.background" - " color.summary.bar" - " color.sync.added" - " color.sync.changed" - " color.sync.rejected" - " color.tagged" - " color.undo.after" - " color.undo.before" - " color.until" - " column.padding" - " complete.all.tags" - " confirmation" - " context" - " data.location" - " dateformat" - " dateformat.annotation" - " dateformat.edit" - " dateformat.holiday" - " dateformat.info" - " dateformat.report" - " date.iso" - " debug" - " debug.hooks" - " debug.parser" - " default.command" - " default.due" - " default.project" - " default.scheduled" - " defaultheight" - " defaultwidth" - " default.timesheet.filter" - " dependency.confirmation" - " dependency.indicator" - " dependency.reminder" - " detection" - " displayweeknumber" - " due" - " editor" - " exit.on.missing.db" - " expressions" - " fontunderline" - " gc" - " hooks" - " hooks.location" - " hyphenate" - " indent.annotation" - " indent.report" - " journal.info" - " journal.time" - " journal.time.start.annotation" - " journal.time.stop.annotation" - " json.array" - " list.all.projects" - " list.all.tags" - " locking" - " nag" - " news.version" - " obfuscate" - " print.empty.columns" - " recurrence" - " recurrence.confirmation" - " recurrence.indicator" - " recurrence.limit" - " regex" - " reserved.lines" - " row.padding" - " rule.color.merge" - " rule.precedence.color" - " search.case.sensitive" - " sugar" - " summary.all.projects" - " sync.local.server_dir" - " sync.gcp.credential_path" - " sync.gcp.bucket" - " sync.server.client_id" - " sync.encryption_secret" - " sync.server.origin" - " tag.indicator" - " undo.style" - " urgency.active.coefficient" - " urgency.scheduled.coefficient" - " urgency.annotations.coefficient" - " urgency.blocked.coefficient" - " urgency.blocking.coefficient" - " urgency.due.coefficient" - " urgency.project.coefficient" - " urgency.tags.coefficient" - " urgency.waiting.coefficient" - " urgency.age.coefficient" - " urgency.age.max" - " urgency.inherit" - " verbose" - " weekstart" - " xterm.title" - " "; + " abbreviation.minimum" + " active.indicator" + " allow.empty.filter" + " avoidlastcolumn" + " bulk" + " calendar.details" + " calendar.details.report" + " calendar.holidays" + " calendar.legend" + " calendar.monthsperline" + " calendar.offset" + " calendar.offset.value" + " color" + " color.active" + " color.alternate" + " color.blocked" + " color.blocking" + " color.burndown.done" + " color.burndown.pending" + " color.burndown.started" + " color.calendar.due" + " color.calendar.due.today" + " color.calendar.holiday" + " color.calendar.scheduled" + " color.calendar.overdue" + " color.calendar.today" + " color.calendar.weekend" + " color.calendar.weeknumber" + " color.completed" + " color.debug" + " color.deleted" + " color.due" + " color.due.today" + " color.warning" + " color.error" + " color.footnote" + " color.header" + " color.history.add" + " color.history.delete" + " color.history.done" + " color.label" + " color.label.sort" + " color.overdue" + " color.recurring" + " color.scheduled" + " color.summary.background" + " color.summary.bar" + " color.sync.added" + " color.sync.changed" + " color.sync.rejected" + " color.tagged" + " color.undo.after" + " color.undo.before" + " color.until" + " column.padding" + " complete.all.tags" + " confirmation" + " context" + " data.location" + " dateformat" + " dateformat.annotation" + " dateformat.edit" + " dateformat.holiday" + " dateformat.info" + " dateformat.report" + " date.iso" + " debug" + " debug.hooks" + " debug.parser" + " default.command" + " default.due" + " default.project" + " default.scheduled" + " defaultheight" + " defaultwidth" + " default.timesheet.filter" + " dependency.confirmation" + " dependency.indicator" + " dependency.reminder" + " detection" + " displayweeknumber" + " due" + " editor" + " exit.on.missing.db" + " purge.on-sync" + " expressions" + " fontunderline" + " gc" + " hooks" + " hooks.location" + " hyphenate" + " indent.annotation" + " indent.report" + " journal.info" + " journal.time" + " journal.time.start.annotation" + " journal.time.stop.annotation" + " json.array" + " limit" + " list.all.projects" + " list.all.tags" + " nag" + " news.version" + " obfuscate" + " print.empty.columns" + " recurrence" + " recurrence.confirmation" + " recurrence.indicator" + " recurrence.limit" + " regex" + " reserved.lines" + " row.padding" + " rule.color.merge" + " rule.precedence.color" + " search.case.sensitive" + " sugar" + " summary.all.projects" + " sync.aws.access_key_id" + " sync.aws.bucket" + " sync.aws.default_credentials" + " sync.aws.profile" + " sync.aws.region" + " sync.aws.secret_access_key" + " sync.gcp.credential_path" + " sync.gcp.bucket" + " sync.local.server_dir" + " sync.server.client_id" + " sync.encryption_secret" + " sync.server.url" + " sync.server.origin" + " tag.indicator" + " urgency.active.coefficient" + " urgency.scheduled.coefficient" + " urgency.annotations.coefficient" + " urgency.blocked.coefficient" + " urgency.blocking.coefficient" + " urgency.due.coefficient" + " urgency.project.coefficient" + " urgency.tags.coefficient" + " urgency.waiting.coefficient" + " urgency.age.coefficient" + " urgency.age.max" + " urgency.inherit" + " verbose" + " weekstart" + " xterm.title" + " "; // This configuration variable is supported, but not documented. It exists // so that unit tests can force color to be on even when the output from task // is redirected to a file, or stdout is not a tty. recognized += "_forcecolor "; - std::vector unrecognized; - for (auto& i : Context::getContext ().config) - { + std::vector unrecognized; + for (auto& i : Context::getContext().config) { // Disallow partial matches by tacking a leading and trailing space on each // variable name. std::string pattern = ' ' + i.first + ' '; - if (recognized.find (pattern) == std::string::npos) - { + if (recognized.find(pattern) == std::string::npos) { // These are special configuration variables, because their name is // dynamic. - if (i.first.substr (0, 14) != "color.keyword." && - i.first.substr (0, 14) != "color.project." && - i.first.substr (0, 10) != "color.tag." && - i.first.substr (0, 10) != "color.uda." && - i.first.substr (0, 8) != "context." && - i.first.substr (0, 8) != "holiday." && - i.first.substr (0, 7) != "report." && - i.first.substr (0, 6) != "alias." && - i.first.substr (0, 5) != "hook." && - i.first.substr (0, 4) != "uda." && - i.first.substr (0, 8) != "default." && - i.first.substr (0, 21) != "urgency.user.project." && - i.first.substr (0, 17) != "urgency.user.tag." && - i.first.substr (0, 21) != "urgency.user.keyword." && - i.first.substr (0, 12) != "urgency.uda.") - { - unrecognized.push_back (i.first); + if (i.first.substr(0, 14) != "color.keyword." && i.first.substr(0, 14) != "color.project." && + i.first.substr(0, 10) != "color.tag." && i.first.substr(0, 10) != "color.uda." && + i.first.substr(0, 8) != "context." && i.first.substr(0, 8) != "holiday." && + i.first.substr(0, 7) != "report." && i.first.substr(0, 6) != "alias." && + i.first.substr(0, 5) != "hook." && i.first.substr(0, 4) != "uda." && + i.first.substr(0, 8) != "default." && i.first.substr(0, 21) != "urgency.user.project." && + i.first.substr(0, 17) != "urgency.user.tag." && + i.first.substr(0, 21) != "urgency.user.keyword." && + i.first.substr(0, 12) != "urgency.uda.") { + unrecognized.push_back(i.first); } } } // Find all the values that match the defaults, for highlighting. - std::vector default_values; + std::vector default_values; Configuration default_config; - default_config.parse (configurationDefaults); + default_config.parse(configurationDefaults); - for (auto& i : Context::getContext ().config) - if (i.second != default_config.get (i.first)) - default_values.push_back (i.first); + for (auto& i : Context::getContext().config) + if (i.second != default_config.get(i.first)) default_values.push_back(i.first); // Create output view. Table view; - view.width (width); - view.add ("Config Variable"); - view.add ("Value"); - setHeaderUnderline (view); + view.width(width); + view.add("Config Variable"); + view.add("Value"); + setHeaderUnderline(view); Color error; Color warning; - if (Context::getContext ().color ()) - { - error = Color (Context::getContext ().config.get ("color.error")); - warning = Color (Context::getContext ().config.get ("color.warning")); + if (Context::getContext().color()) { + error = Color(Context::getContext().config.get("color.error")); + warning = Color(Context::getContext().config.get("color.warning")); } bool issue_error = false; @@ -282,145 +280,120 @@ int CmdShow::execute (std::string& output) std::string section; - // Look for the first plausible argument which could be a pattern - if (words.size ()) - section = words[0]; + // Look for the first plausible argument which could be a pattern + if (words.size()) section = words[0]; - if (section == "all") - section = ""; + if (section == "all") section = ""; std::string::size_type loc; - for (auto& i : Context::getContext ().config) - { - loc = i.first.find (section, 0); - if (loc != std::string::npos) - { + for (auto& i : Context::getContext().config) { + loc = i.first.find(section, 0); + if (loc != std::string::npos) { // Look for unrecognized. Color color; - if (std::find (unrecognized.begin (), unrecognized.end (), i.first) != unrecognized.end ()) - { + if (std::find(unrecognized.begin(), unrecognized.end(), i.first) != unrecognized.end()) { issue_error = true; color = error; - } - else if (std::find (default_values.begin (), default_values.end (), i.first) != default_values.end ()) - { + } else if (std::find(default_values.begin(), default_values.end(), i.first) != + default_values.end()) { issue_warning = true; color = warning; } std::string value = i.second; - int row = view.addRow (); - view.set (row, 0, i.first, color); - view.set (row, 1, value, color); + int row = view.addRow(); + view.set(row, 0, i.first, color); + view.set(row, 1, value, color); - if (default_config[i.first] != value && - default_config[i.first] != "") - { - row = view.addRow (); - view.set (row, 0, std::string (" ") + "Default value", color); - view.set (row, 1, default_config[i.first], color); + if (default_config[i.first] != value && default_config[i.first] != "") { + row = view.addRow(); + view.set(row, 0, std::string(" ") + "Default value", color); + view.set(row, 1, default_config[i.first], color); } } } out << '\n' - << view.render () - << (view.rows () == 0 ? "No matching configuration variables." : "") - << (view.rows () == 0 ? "\n\n" : "\n"); + << view.render() << (view.rows() == 0 ? "No matching configuration variables." : "") + << (view.rows() == 0 ? "\n\n" : "\n"); - if (issue_warning) - { + if (issue_warning) { out << "Some of your .taskrc variables differ from the default values.\n"; - if (Context::getContext ().color () && warning.nontrivial ()) - out << " " - << format (STRING_CMD_SHOW_DIFFER_COLOR, warning.colorize ("color")) - << "\n\n"; + if (Context::getContext().color() && warning.nontrivial()) + out << " " << format(STRING_CMD_SHOW_DIFFER_COLOR, warning.colorize("color")) << "\n\n"; } // Display the unrecognized variables. - if (issue_error) - { + if (issue_error) { out << "Your .taskrc file contains these unrecognized variables:\n"; - for (auto& i : unrecognized) - out << " " << i << '\n'; + for (auto& i : unrecognized) out << " " << i << '\n'; - if (Context::getContext ().color () && error.nontrivial ()) - out << '\n' << format (STRING_CMD_SHOW_DIFFER_COLOR, error.colorize ("color")); + if (Context::getContext().color() && error.nontrivial()) + out << '\n' << format(STRING_CMD_SHOW_DIFFER_COLOR, error.colorize("color")); out << "\n\n"; } - out << legacyCheckForDeprecatedVariables (); - out << legacyCheckForDeprecatedColumns (); + out << legacyCheckForDeprecatedVariables(); + out << legacyCheckForDeprecatedColumns(); // TODO Check for referenced but missing theme files. // TODO Check for referenced but missing string files. // Check for bad values in rc.calendar.details. - std::string calendardetails = Context::getContext ().config.get ("calendar.details"); - if (calendardetails != "full" && - calendardetails != "sparse" && - calendardetails != "none") - out << format (STRING_CMD_SHOW_CONFIG_ERROR, "calendar.details", calendardetails) - << '\n'; + std::string calendardetails = Context::getContext().config.get("calendar.details"); + if (calendardetails != "full" && calendardetails != "sparse" && calendardetails != "none") + out << format(STRING_CMD_SHOW_CONFIG_ERROR, "calendar.details", calendardetails) << '\n'; // Check for bad values in rc.calendar.holidays. - std::string calendarholidays = Context::getContext ().config.get ("calendar.holidays"); - if (calendarholidays != "full" && - calendarholidays != "sparse" && - calendarholidays != "none") - out << format (STRING_CMD_SHOW_CONFIG_ERROR, "calendar.holidays", calendarholidays) - << '\n'; + std::string calendarholidays = Context::getContext().config.get("calendar.holidays"); + if (calendarholidays != "full" && calendarholidays != "sparse" && calendarholidays != "none") + out << format(STRING_CMD_SHOW_CONFIG_ERROR, "calendar.holidays", calendarholidays) << '\n'; // Verify installation. This is mentioned in the documentation as the way // to ensure everything is properly installed. - if (Context::getContext ().config.size () == 0) - { + if (Context::getContext().config.size() == 0) { out << "Configuration error: .taskrc contains no entries.\n"; rc = 1; - } - else - { - Directory location (Context::getContext ().config.get ("data.location")); + } else { + Directory location(Context::getContext().config.get("data.location")); if (location._data == "") out << "Configuration error: data.location not specified in .taskrc file.\n"; - if (! location.exists ()) - out << "Configuration error: data.location contains a directory name that doesn't exist, or is unreadable.\n"; + if (!location.exists()) + out << "Configuration error: data.location contains a directory name that doesn't exist, or " + "is unreadable.\n"; } - output = out.str (); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdShowRaw::CmdShowRaw () -{ - _keyword = "_show"; - _usage = "task _show"; +CmdShowRaw::CmdShowRaw() { + _keyword = "_show"; + _usage = "task _show"; _description = "Shows all configuration settings in a machine-readable format"; - _read_only = true; + _read_only = true; _displays_id = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdShowRaw::execute (std::string& output) -{ +int CmdShowRaw::execute(std::string& output) { // Get all the settings and sort alphabetically by name. - auto all = Context::getContext ().config.all (); - std::sort (all.begin (), all.end ()); + auto all = Context::getContext().config.all(); + std::sort(all.begin(), all.end()); // Display them all. std::stringstream out; - for (auto& i : all) - out << i << '=' << Context::getContext ().config.get (i) << '\n'; + for (auto& i : all) out << i << '=' << Context::getContext().config.get(i) << '\n'; - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdShow.h b/src/commands/CmdShow.h index 2bee52518..cc3f064fd 100644 --- a/src/commands/CmdShow.h +++ b/src/commands/CmdShow.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDSHOW #define INCLUDED_CMDSHOW -#include #include -class CmdShow : public Command -{ -public: - CmdShow (); - int execute (std::string&); +#include + +class CmdShow : public Command { + public: + CmdShow(); + int execute(std::string&); }; -class CmdShowRaw : public Command -{ -public: - CmdShowRaw (); - int execute (std::string&); +class CmdShowRaw : public Command { + public: + CmdShowRaw(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdStart.cpp b/src/commands/CmdStart.cpp index 6329c8129..057b61fc5 100644 --- a/src/commands/CmdStart.cpp +++ b/src/commands/CmdStart.cpp @@ -25,116 +25,107 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include +#include #include +#include +#include #include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdStart::CmdStart () -{ - _keyword = "start"; - _usage = "task start "; - _description = "Marks specified task as started"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdStart::CmdStart() { + _keyword = "start"; + _usage = "task start "; + _description = "Marks specified task as started"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdStart::execute (std::string&) -{ +int CmdStart::execute(std::string&) { int rc = 0; int count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - std::vector modified; - for (auto& task : filtered) - { - if (! task.has ("start")) - { - Task before (task); + std::vector modified; + for (auto& task : filtered) { + if (!task.has("start")) { + Task before(task); // Start the specified task. - std::string question = format ("Start task {1} '{2}'?", - task.identifier (true), - task.get ("description")); - task.modify (Task::modAnnotate); - task.setAsNow ("start"); + std::string question = + format("Start task {1} '{2}'?", task.identifier(true), task.get("description")); + task.setAsNow("start"); - Task::status status = task.getStatus (); - if (status == Task::completed || status == Task::deleted) - { + Task::status status = task.getStatus(); + if (status == Task::completed || status == Task::deleted) { // "waiting" handled by Task::validate(), no special care needed here. - task.setStatus (Task::pending); + task.setStatus(Task::pending); } - if (Context::getContext ().config.getBoolean ("journal.time")) - task.addAnnotation (Context::getContext ().config.get ("journal.time.start.annotation")); + task.modify(Task::modAnnotate); + if (Context::getContext().config.getBoolean("journal.time")) + task.addAnnotation(Context::getContext().config.get("journal.time.start.annotation")); - if (permission (before.diff (task) + question, filtered.size ())) - { - updateRecurrenceMask (task); - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + updateRecurrenceMask(task); + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Starting task {1} '{2}'.", task); - dependencyChainOnStart (task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); + feedback_affected("Starting task {1} '{2}'.", task); + dependencyChainOnStart(task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); // Save unmodified task for potential nagging later modified.push_back(before); - } - else - { + } else { std::cout << "Task not started.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } - } - else - { - std::cout << format ("Task {1} '{2}' already started.", - task.id, - task.get ("description")) + } else { + std::cout << format("Task {1} '{2}' already started.", task.id, task.get("description")) << '\n'; rc = 1; } } - nag (modified); + nag(modified); // Now list the project changes. for (auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Started {1} task." : "Started {1} tasks.", count); + feedback_affected(count == 1 ? "Started {1} task." : "Started {1} tasks.", count); return rc; } diff --git a/src/commands/CmdStart.h b/src/commands/CmdStart.h index 81951de17..2c16f46d8 100644 --- a/src/commands/CmdStart.h +++ b/src/commands/CmdStart.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDSTART #define INCLUDED_CMDSTART -#include #include -class CmdStart : public Command -{ -public: - CmdStart (); - int execute (std::string&); +#include + +class CmdStart : public Command { + public: + CmdStart(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdStats.cpp b/src/commands/CmdStats.cpp index 668b1cf0d..8b5860ea1 100644 --- a/src/commands/CmdStats.cpp +++ b/src/commands/CmdStats.cpp @@ -25,257 +25,251 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include -#include +#include #include #include -#include #include -#include +#include #include +#include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdStats::CmdStats () -{ - _keyword = "stats"; - _usage = "task stats"; - _description = "Shows task database statistics"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdStats::CmdStats() { + _keyword = "stats"; + _usage = "task stats"; + _description = "Shows task database statistics"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdStats::execute (std::string& output) -{ +int CmdStats::execute(std::string& output) { int rc = 0; std::stringstream out; - std::string dateformat = Context::getContext ().config.get ("dateformat"); + std::string dateformat = Context::getContext().config.get("dateformat"); // Count the possible reverts. - int undoCount = Context::getContext ().tdb2.num_reverts_possible (); + int undoCount = Context::getContext().tdb2.num_reverts_possible(); // Count the backlog transactions. - int numLocalChanges = Context::getContext ().tdb2.num_local_changes (); + int numLocalChanges = Context::getContext().tdb2.num_local_changes(); // Get all the tasks. Filter filter; - std::vector all = Context::getContext ().tdb2.all_tasks (); - std::vector filtered; - filter.subset (all, filtered); + std::vector all = Context::getContext().tdb2.all_tasks(); + std::vector filtered; + filter.subset(all, filtered); Datetime now; - time_t earliest = time (nullptr); - time_t latest = 1; - int totalT = 0; - int deletedT = 0; - int pendingT = 0; - int completedT = 0; - int waitingT = 0; - int taggedT = 0; - int annotationsT = 0; - int recurringT = 0; - int blockingT = 0; - int blockedT = 0; + time_t earliest = time(nullptr); + time_t latest = 1; + int totalT = 0; + int deletedT = 0; + int pendingT = 0; + int completedT = 0; + int waitingT = 0; + int taggedT = 0; + int annotationsT = 0; + int recurringT = 0; + int blockingT = 0; + int blockedT = 0; float daysPending = 0.0; - int descLength = 0; - std::map allTags; - std::map allProjects; + int descLength = 0; + std::map allTags; + std::map allProjects; - for (auto& task : filtered) - { + for (auto& task : filtered) { ++totalT; - Task::status status = task.getStatus (); - switch (status) - { - case Task::deleted: ++deletedT; break; - case Task::pending: ++pendingT; break; - case Task::completed: ++completedT; break; - case Task::recurring: ++recurringT; break; - case Task::waiting: ++waitingT; break; + Task::status status = task.getStatus(); + switch (status) { + case Task::deleted: + ++deletedT; + break; + case Task::pending: + ++pendingT; + break; + case Task::completed: + ++completedT; + break; + case Task::recurring: + ++recurringT; + break; + case Task::waiting: + ++waitingT; + break; } - if (task.is_blocked) ++blockedT; + if (task.is_blocked) ++blockedT; if (task.is_blocking) ++blockingT; - time_t entry = strtoll (task.get ("entry").c_str (), nullptr, 10); + time_t entry = strtoll(task.get("entry").c_str(), nullptr, 10); if (entry < earliest) earliest = entry; - if (entry > latest) latest = entry; + if (entry > latest) latest = entry; - if (status == Task::completed) - { - time_t end = strtoll (task.get ("end").c_str (), nullptr, 10); + if (status == Task::completed) { + time_t end = strtoll(task.get("end").c_str(), nullptr, 10); daysPending += (end - entry) / 86400.0; } - if (status == Task::pending) - daysPending += (now.toEpoch () - entry) / 86400.0; + if (status == Task::pending) daysPending += (now.toEpoch() - entry) / 86400.0; - descLength += task.get ("description").length (); - annotationsT += task.getAnnotations ().size (); + descLength += task.get("description").length(); + annotationsT += task.getAnnotations().size(); - auto tags = task.getTags (); - if (tags.size ()) - ++taggedT; + auto tags = task.getTags(); + if (tags.size()) ++taggedT; - for (auto& tag : tags) - allTags[tag] = 0; + for (auto& tag : tags) allTags[tag] = 0; - std::string project = task.get ("project"); - if (project != "") - allProjects[project] = 0; + std::string project = task.get("project"); + if (project != "") allProjects[project] = 0; } // Create a table for output. Table view; - view.width (Context::getContext ().getWidth ()); - view.intraPadding (2); - view.add ("Category"); - view.add ("Data"); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.intraPadding(2); + view.add("Category"); + view.add("Data"); + setHeaderUnderline(view); - int row = view.addRow (); - view.set (row, 0, "Pending"); - view.set (row, 1, pendingT); + int row = view.addRow(); + view.set(row, 0, "Pending"); + view.set(row, 1, pendingT); - row = view.addRow (); - view.set (row, 0, "Waiting"); - view.set (row, 1, waitingT); + row = view.addRow(); + view.set(row, 0, "Waiting"); + view.set(row, 1, waitingT); - row = view.addRow (); - view.set (row, 0, "Recurring"); - view.set (row, 1, recurringT); + row = view.addRow(); + view.set(row, 0, "Recurring"); + view.set(row, 1, recurringT); - row = view.addRow (); - view.set (row, 0, "Completed"); - view.set (row, 1, completedT); + row = view.addRow(); + view.set(row, 0, "Completed"); + view.set(row, 1, completedT); - row = view.addRow (); - view.set (row, 0, "Deleted"); - view.set (row, 1, deletedT); + row = view.addRow(); + view.set(row, 0, "Deleted"); + view.set(row, 1, deletedT); - row = view.addRow (); - view.set (row, 0, "Total"); - view.set (row, 1, totalT); + row = view.addRow(); + view.set(row, 0, "Total"); + view.set(row, 1, totalT); - row = view.addRow (); - view.set (row, 0, "Annotations"); - view.set (row, 1, annotationsT); + row = view.addRow(); + view.set(row, 0, "Annotations"); + view.set(row, 1, annotationsT); - row = view.addRow (); - view.set (row, 0, "Unique tags"); - view.set (row, 1, (int)allTags.size ()); + row = view.addRow(); + view.set(row, 0, "Unique tags"); + view.set(row, 1, (int)allTags.size()); - row = view.addRow (); - view.set (row, 0, "Projects"); - view.set (row, 1, (int)allProjects.size ()); + row = view.addRow(); + view.set(row, 0, "Projects"); + view.set(row, 1, (int)allProjects.size()); - row = view.addRow (); - view.set (row, 0, "Blocked tasks"); - view.set (row, 1, blockedT); + row = view.addRow(); + view.set(row, 0, "Blocked tasks"); + view.set(row, 1, blockedT); - row = view.addRow (); - view.set (row, 0, "Blocking tasks"); - view.set (row, 1, blockingT); + row = view.addRow(); + view.set(row, 0, "Blocking tasks"); + view.set(row, 1, blockingT); - row = view.addRow (); - view.set (row, 0, "Undo transactions"); - view.set (row, 1, undoCount); + row = view.addRow(); + view.set(row, 0, "Undo transactions"); + view.set(row, 1, undoCount); - row = view.addRow (); - view.set (row, 0, "Sync backlog transactions"); - view.set (row, 1, numLocalChanges); + row = view.addRow(); + view.set(row, 0, "Sync backlog transactions"); + view.set(row, 1, numLocalChanges); - if (totalT) - { - row = view.addRow (); - view.set (row, 0, "Tasks tagged"); + if (totalT) { + row = view.addRow(); + view.set(row, 0, "Tasks tagged"); std::stringstream value; - value << std::setprecision (3) << (100.0 * taggedT / totalT) << '%'; - view.set (row, 1, value.str ()); + value << std::setprecision(3) << (100.0 * taggedT / totalT) << '%'; + view.set(row, 1, value.str()); } - if (filtered.size ()) - { - Datetime e (earliest); - row = view.addRow (); - view.set (row, 0, "Oldest task"); - view.set (row, 1, e.toString (dateformat)); + if (filtered.size()) { + Datetime e(earliest); + row = view.addRow(); + view.set(row, 0, "Oldest task"); + view.set(row, 1, e.toString(dateformat)); - Datetime l (latest); - row = view.addRow (); - view.set (row, 0, "Newest task"); - view.set (row, 1, l.toString (dateformat)); + Datetime l(latest); + row = view.addRow(); + view.set(row, 0, "Newest task"); + view.set(row, 1, l.toString(dateformat)); - row = view.addRow (); - view.set (row, 0, "Task used for"); - view.set (row, 1, Duration (latest - earliest).formatVague ()); + row = view.addRow(); + view.set(row, 0, "Task used for"); + view.set(row, 1, Duration(latest - earliest).formatVague()); } - if (totalT) - { - row = view.addRow (); - view.set (row, 0, "Task added every"); - view.set (row, 1, Duration (((latest - earliest) / totalT)).formatVague ()); + if (totalT) { + row = view.addRow(); + view.set(row, 0, "Task added every"); + view.set(row, 1, Duration(((latest - earliest) / totalT)).formatVague()); } - if (completedT) - { - row = view.addRow (); - view.set (row, 0, "Task completed every"); - view.set (row, 1, Duration ((latest - earliest) / completedT).formatVague ()); + if (completedT) { + row = view.addRow(); + view.set(row, 0, "Task completed every"); + view.set(row, 1, Duration((latest - earliest) / completedT).formatVague()); } - if (deletedT) - { - row = view.addRow (); - view.set (row, 0, "Task deleted every"); - view.set (row, 1, Duration ((latest - earliest) / deletedT).formatVague ()); + if (deletedT) { + row = view.addRow(); + view.set(row, 0, "Task deleted every"); + view.set(row, 1, Duration((latest - earliest) / deletedT).formatVague()); } - if (pendingT || completedT) - { - row = view.addRow (); - view.set (row, 0, "Average time pending"); - view.set (row, 1, Duration ((int) ((daysPending / (pendingT + completedT)) * 86400)).formatVague ()); + if (pendingT || completedT) { + row = view.addRow(); + view.set(row, 0, "Average time pending"); + view.set(row, 1, + Duration((int)((daysPending / (pendingT + completedT)) * 86400)).formatVague()); } - if (totalT) - { - row = view.addRow (); - view.set (row, 0, "Average desc length"); - view.set (row, 1, format ("{1} characters", (int) (descLength / totalT))); + if (totalT) { + row = view.addRow(); + view.set(row, 0, "Average desc length"); + view.set(row, 1, format("{1} characters", (int)(descLength / totalT))); } // If an alternating row color is specified, notify the table. - if (Context::getContext ().color ()) - { - Color alternate (Context::getContext ().config.get ("color.alternate")); - if (alternate.nontrivial ()) - { - view.colorOdd (alternate); - view.intraColorOdd (alternate); - view.extraColorOdd (alternate); + if (Context::getContext().color()) { + Color alternate(Context::getContext().config.get("color.alternate")); + if (alternate.nontrivial()) { + view.colorOdd(alternate); + view.intraColorOdd(alternate); + view.extraColorOdd(alternate); } } - out << optionalBlankLine () - << view.render () - << optionalBlankLine (); + out << optionalBlankLine() << view.render() << optionalBlankLine(); - output = out.str (); + output = out.str(); return rc; } diff --git a/src/commands/CmdStats.h b/src/commands/CmdStats.h index 2d384c434..b62cbdfd0 100644 --- a/src/commands/CmdStats.h +++ b/src/commands/CmdStats.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDSTATS #define INCLUDED_CMDSTATS -#include #include -class CmdStats : public Command -{ -public: - CmdStats (); - int execute (std::string&); +#include + +class CmdStats : public Command { + public: + CmdStats(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdStop.cpp b/src/commands/CmdStop.cpp index f9d1cec6d..9924c5a58 100644 --- a/src/commands/CmdStop.cpp +++ b/src/commands/CmdStop.cpp @@ -25,91 +25,84 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include #include #include -#include +#include +#include #include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -CmdStop::CmdStop () -{ - _keyword = "stop"; - _usage = "task stop "; - _description = "Removes the 'start' time from a task"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = true; - _accepts_filter = true; +CmdStop::CmdStop() { + _keyword = "stop"; + _usage = "task stop "; + _description = "Removes the 'start' time from a task"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = true; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdStop::execute (std::string&) -{ +int CmdStop::execute(std::string&) { int rc = 0; int count = 0; // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + std::vector filtered; + filter.subset(filtered); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Accumulated project change notifications. - std::map projectChanges; + std::map projectChanges; - if(filtered.size() > 1) { + if (filtered.size() > 1) { feedback_affected("This command will alter {1} tasks.", filtered.size()); } - for (auto& task : filtered) - { - if (task.has ("start")) - { - Task before (task); + for (auto& task : filtered) { + if (task.has("start")) { + Task before(task); // Stop the specified task. - std::string question = format ("Stop task {1} '{2}'?", - task.identifier (true), - task.get ("description")); + std::string question = + format("Stop task {1} '{2}'?", task.identifier(true), task.get("description")); - task.modify (Task::modAnnotate); - task.remove ("start"); + task.modify(Task::modAnnotate); + task.remove("start"); - if (Context::getContext ().config.getBoolean ("journal.time")) - task.addAnnotation (Context::getContext ().config.get ("journal.time.stop.annotation")); + if (Context::getContext().config.getBoolean("journal.time")) + task.addAnnotation(Context::getContext().config.get("journal.time.stop.annotation")); - if (permission (before.diff (task) + question, filtered.size ())) - { - updateRecurrenceMask (task); - Context::getContext ().tdb2.modify (task); + if (permission(before.diff(task) + question, filtered.size())) { + updateRecurrenceMask(task); + Context::getContext().tdb2.modify(task); ++count; - feedback_affected ("Stopping task {1} '{2}'.", task); - dependencyChainOnStart (task); - if (Context::getContext ().verbose ("project")) - projectChanges[task.get ("project")] = onProjectChange (task, false); - } - else - { + feedback_affected("Stopping task {1} '{2}'.", task); + dependencyChainOnStart(task); + if (Context::getContext().verbose("project")) + projectChanges[task.get("project")] = onProjectChange(task, false); + } else { std::cout << "Task not stopped.\n"; rc = 1; - if (_permission_quit) - break; + if (_permission_quit) break; } - } - else - { - std::cout << format ("Task {1} '{2}' not started.", - task.identifier (true), - task.get ("description")) + } else { + std::cout << format("Task {1} '{2}' not started.", task.identifier(true), + task.get("description")) << '\n'; rc = 1; } @@ -117,10 +110,9 @@ int CmdStop::execute (std::string&) // Now list the project changes. for (auto& change : projectChanges) - if (change.first != "") - Context::getContext ().footnote (change.second); + if (change.first != "") Context::getContext().footnote(change.second); - feedback_affected (count == 1 ? "Stopped {1} task." : "Stopped {1} tasks.", count); + feedback_affected(count == 1 ? "Stopped {1} task." : "Stopped {1} tasks.", count); return rc; } diff --git a/src/commands/CmdStop.h b/src/commands/CmdStop.h index fb28a444c..695a4c25c 100644 --- a/src/commands/CmdStop.h +++ b/src/commands/CmdStop.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDSTOP #define INCLUDED_CMDSTOP -#include #include -class CmdStop : public Command -{ -public: - CmdStop (); - int execute (std::string&); +#include + +class CmdStop : public Command { + public: + CmdStop(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdSummary.cpp b/src/commands/CmdSummary.cpp index 4a23384ba..ca102638e 100644 --- a/src/commands/CmdSummary.cpp +++ b/src/commands/CmdSummary.cpp @@ -25,187 +25,161 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include +#include #include #include -#include #include +#include +#include #include -#include + #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdSummary::CmdSummary () -{ - _keyword = "summary"; - _usage = "task summary"; - _description = "Shows a report of task status by project"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdSummary::CmdSummary() { + _keyword = "summary"; + _usage = "task summary"; + _description = "Shows a report of task status by project"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::graphs; + _category = Command::Category::graphs; } //////////////////////////////////////////////////////////////////////////////// // Project Remaining Avg Age Complete 0% 100% // A 12 13d 55% XXXXXXXXXXXXX----------- // B 109 3d 12h 10% XXX--------------------- -int CmdSummary::execute (std::string& output) -{ +int CmdSummary::execute(std::string& output) { int rc = 0; - bool showAllProjects = Context::getContext ().config.getBoolean ("summary.all.projects"); + bool showAllProjects = Context::getContext().config.getBoolean("summary.all.projects"); // Apply filter. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Generate unique list of project names from all pending tasks. - std::map allProjects; + std::map allProjects; for (auto& task : filtered) - if (showAllProjects || task.getStatus () == Task::pending) - allProjects[task.get ("project")] = false; + if (showAllProjects || task.getStatus() == Task::pending) + allProjects[task.get("project")] = false; // Initialize counts, sum. - std::map countPending; - std::map countCompleted; - std::map sumEntry; - std::map counter; - time_t now = time (nullptr); + std::map countPending; + std::map countCompleted; + std::map sumEntry; + std::map counter; + time_t now = time(nullptr); // Initialize counters. - for (auto& project : allProjects) - { - countPending [project.first] = 0; - countCompleted [project.first] = 0; - sumEntry [project.first] = 0.0; - counter [project.first] = 0; + for (auto& project : allProjects) { + countPending[project.first] = 0; + countCompleted[project.first] = 0; + sumEntry[project.first] = 0.0; + counter[project.first] = 0; } // Count the various tasks. - for (auto& task : filtered) - { - std::string project = task.get ("project"); - std::vector projects = extractParents (project); - projects.push_back (project); + for (auto& task : filtered) { + std::string project = task.get("project"); + std::vector projects = extractParents(project); + projects.push_back(project); - for (auto& parent : projects) - ++counter[parent]; + for (auto& parent : projects) ++counter[parent]; - if (task.getStatus () == Task::pending || - task.getStatus () == Task::waiting) - { - for (auto& parent : projects) - { + if (task.getStatus() == Task::pending || task.getStatus() == Task::waiting) { + for (auto& parent : projects) { ++countPending[parent]; - time_t entry = strtoll (task.get ("entry").c_str (), nullptr, 10); - if (entry) - sumEntry[parent] = sumEntry[parent] + (double) (now - entry); + time_t entry = strtoll(task.get("entry").c_str(), nullptr, 10); + if (entry) sumEntry[parent] = sumEntry[parent] + (double)(now - entry); } } - else if (task.getStatus () == Task::completed) - { - for (auto& parent : projects) - { + else if (task.getStatus() == Task::completed) { + for (auto& parent : projects) { ++countCompleted[parent]; - time_t entry = strtoll (task.get ("entry").c_str (), nullptr, 10); - time_t end = strtoll (task.get ("end").c_str (), nullptr, 10); - if (entry && end) - sumEntry[parent] = sumEntry[parent] + (double) (end - entry); + time_t entry = strtoll(task.get("entry").c_str(), nullptr, 10); + time_t end = strtoll(task.get("end").c_str(), nullptr, 10); + if (entry && end) sumEntry[parent] = sumEntry[parent] + (double)(end - entry); } } } // Create a table for output. Table view; - view.width (Context::getContext ().getWidth ()); - view.add ("Project"); - view.add ("Remaining", false); - view.add ("Avg age", false); - view.add ("Complete", false); - view.add ("0% 100%", true, false); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.add("Project"); + view.add("Remaining", false); + view.add("Avg age", false); + view.add("Complete", false); + view.add("0% 100%", true, false); + setHeaderUnderline(view); Color bar_color; Color bg_color; - if (Context::getContext ().color ()) - { - bar_color = Color (Context::getContext ().config.get ("color.summary.bar")); - bg_color = Color (Context::getContext ().config.get ("color.summary.background")); + if (Context::getContext().color()) { + bar_color = Color(Context::getContext().config.get("color.summary.bar")); + bg_color = Color(Context::getContext().config.get("color.summary.background")); } // sort projects into sorted list std::list> sortedProjects; - sort_projects (sortedProjects, allProjects); + sort_projects(sortedProjects, allProjects); int barWidth = 30; // construct view from sorted list - for (auto& i : sortedProjects) - { - int row = view.addRow (); - view.set (row, 0, (i.first == "" - ? "(none)" - : indentProject (i.first, " ", '.'))); + for (auto& i : sortedProjects) { + int row = view.addRow(); + view.set(row, 0, (i.first == "" ? "(none)" : indentProject(i.first, " ", '.'))); - view.set (row, 1, countPending[i.first]); - if (counter[i.first]) - view.set (row, 2, Duration ((int) (sumEntry[i.first] / (double)counter[i.first])).formatVague ()); + view.set(row, 1, countPending[i.first]); + if (counter[i.first]) + view.set(row, 2, Duration((int)(sumEntry[i.first] / (double)counter[i.first])).formatVague()); - int c = countCompleted[i.first]; - int p = countPending[i.first]; - int completedBar = 0; - if (c + p) - completedBar = (c * barWidth) / (c + p); + int c = countCompleted[i.first]; + int p = countPending[i.first]; + int completedBar = 0; + if (c + p) completedBar = (c * barWidth) / (c + p); - std::string bar; - std::string subbar; - if (Context::getContext ().color ()) - { - bar += bar_color.colorize (std::string ( completedBar, ' ')); - bar += bg_color.colorize (std::string (barWidth - completedBar, ' ')); - } - else - { - bar += std::string ( completedBar, '=') - + std::string (barWidth - completedBar, ' '); - } - view.set (row, 4, bar); + std::string bar; + std::string subbar; + if (Context::getContext().color()) { + bar += bar_color.colorize(std::string(completedBar, ' ')); + bar += bg_color.colorize(std::string(barWidth - completedBar, ' ')); + } else { + bar += std::string(completedBar, '=') + std::string(barWidth - completedBar, ' '); + } + view.set(row, 4, bar); - char percent[12] = "0%"; - if (c + p) - snprintf (percent, 12, "%d%%", 100 * c / (c + p)); - view.set (row, 3, percent); + char percent[12] = "0%"; + if (c + p) snprintf(percent, 12, "%d%%", 100 * c / (c + p)); + view.set(row, 3, percent); } std::stringstream out; - if (view.rows ()) - { - out << optionalBlankLine () - << view.render () - << optionalBlankLine (); + if (view.rows()) { + out << optionalBlankLine() << view.render() << optionalBlankLine(); - out << format ("{1} projects\n", view.rows ()); - } - else - { + out << format("{1} projects\n", view.rows()); + } else { out << "No projects.\n"; rc = 1; } - output = out.str (); + output = out.str(); return rc; } diff --git a/src/commands/CmdSummary.h b/src/commands/CmdSummary.h index 012a062b9..abfe521a5 100644 --- a/src/commands/CmdSummary.h +++ b/src/commands/CmdSummary.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDSUMMARY #define INCLUDED_CMDSUMMARY -#include #include -class CmdSummary : public Command -{ -public: - CmdSummary (); - int execute (std::string&); +#include + +class CmdSummary : public Command { + public: + CmdSummary(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdSync.cpp b/src/commands/CmdSync.cpp index 5b0775fb3..e90c31061 100644 --- a/src/commands/CmdSync.cpp +++ b/src/commands/CmdSync.cpp @@ -25,80 +25,157 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include +#include #include #include -#include -#include #include +#include +#include #include -#include "tc/Server.h" + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdSync::CmdSync () -{ - _keyword = "synchronize"; - _usage = "task synchronize [initialize]"; - _description = "Synchronizes data with the Taskserver"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdSync::CmdSync() { + _keyword = "synchronize"; + _usage = "task synchronize [initialize]"; + _description = "Synchronizes data with the Taskserver"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::migration; + _category = Command::Category::migration; } //////////////////////////////////////////////////////////////////////////////// -int CmdSync::execute (std::string& output) -{ +int CmdSync::execute(std::string& output) { int status = 0; - tc::Server server; - std::string server_ident; + Context& context = Context::getContext(); + auto& replica = context.tdb2.replica(); + std::stringstream out; + bool avoid_snapshots = false; + bool verbose = Context::getContext().verbose("sync"); - // If no server is set up, quit. - std::string origin = Context::getContext ().config.get ("sync.server.origin"); - std::string server_dir = Context::getContext ().config.get ("sync.local.server_dir"); - std::string gcp_credential_path = Context::getContext ().config.get ("sync.gcp.credential_path"); - std::string gcp_bucket = Context::getContext ().config.get ("sync.gcp.bucket"); - std::string encryption_secret = Context::getContext ().config.get ("sync.encryption_secret"); - if (server_dir != "") { - server = tc::Server::new_local (server_dir); - server_ident = server_dir; - } else if (gcp_bucket != "") { - if (encryption_secret == "") { - throw std::string ("sync.encryption_secret is required"); - } - server = tc::Server::new_gcp (gcp_bucket, gcp_credential_path, encryption_secret); - std::ostringstream os; - os << "GCP bucket " << gcp_bucket; - server_ident = os.str(); - } else if (origin != "") { - std::string client_id = Context::getContext ().config.get ("sync.server.client_id"); - if (client_id == "" || encryption_secret == "") { - throw std::string ("sync.server.client_id and sync.encryption_secret are required"); - } - server = tc::Server::new_sync (origin, client_id, encryption_secret); - std::ostringstream os; - os << "Sync server at " << origin; - server_ident = os.str(); - } else { - throw std::string ("No sync.* settings are configured. See task-sync(5)."); + std::string origin = Context::getContext().config.get("sync.server.origin"); + std::string url = Context::getContext().config.get("sync.server.url"); + std::string server_dir = Context::getContext().config.get("sync.local.server_dir"); + std::string client_id = Context::getContext().config.get("sync.server.client_id"); + std::string aws_bucket = Context::getContext().config.get("sync.aws.bucket"); + std::string gcp_bucket = Context::getContext().config.get("sync.gcp.bucket"); + std::string encryption_secret = Context::getContext().config.get("sync.encryption_secret"); + + // sync.server.origin is a deprecated synonym for sync.server.url + std::string server_url = url == "" ? origin : url; + if (origin != "") { + out << "sync.server.origin is deprecated. Use sync.server.url instead.\n"; } - std::stringstream out; - if (Context::getContext ().verbose ("sync")) - out << format ("Syncing with {1}", server_ident) - << '\n'; + // redact credentials from `server_url`, if present + std::regex remove_creds_regex("^(https?://.+):(.+)@(.+)"); + std::string safe_server_url = std::regex_replace(server_url, remove_creds_regex, "$1:****@$3"); - Context::getContext ().tdb2.sync(std::move(server), false); + auto num_local_operations = replica->num_local_operations(); - output = out.str (); + if (server_dir != "") { + if (verbose) { + out << format("Syncing with {1}", server_dir) << '\n'; + } + replica->sync_to_local(server_dir, avoid_snapshots); + } else if (aws_bucket != "") { + std::string aws_region = Context::getContext().config.get("sync.aws.region"); + std::string aws_profile = Context::getContext().config.get("sync.aws.profile"); + std::string aws_access_key_id = Context::getContext().config.get("sync.aws.access_key_id"); + std::string aws_secret_access_key = + Context::getContext().config.get("sync.aws.secret_access_key"); + std::string aws_default_credentials = + Context::getContext().config.get("sync.aws.default_credentials"); + if (aws_region == "") { + throw std::string("sync.aws.region is required"); + } + if (encryption_secret == "") { + throw std::string("sync.encryption_secret is required"); + } + + bool using_profile = false; + bool using_creds = false; + bool using_default = false; + if (aws_profile != "") { + using_profile = true; + } + if (aws_access_key_id != "" || aws_secret_access_key != "") { + using_creds = true; + } + if (aws_default_credentials != "") { + using_default = true; + } + + if (using_profile + using_creds + using_default != 1) { + throw std::string("exactly one method of specifying AWS credentials is required"); + } + + if (verbose) { + out << format("Syncing with AWS bucket {1}", aws_bucket) << '\n'; + } + + if (using_profile) { + replica->sync_to_aws_with_profile(aws_region, aws_bucket, aws_profile, encryption_secret, + avoid_snapshots); + } else if (using_creds) { + replica->sync_to_aws_with_access_key(aws_region, aws_bucket, aws_access_key_id, + aws_secret_access_key, encryption_secret, + avoid_snapshots); + } else { + replica->sync_to_aws_with_default_creds(aws_region, aws_bucket, encryption_secret, + avoid_snapshots); + } + + } else if (gcp_bucket != "") { + std::string gcp_credential_path = Context::getContext().config.get("sync.gcp.credential_path"); + if (encryption_secret == "") { + throw std::string("sync.encryption_secret is required"); + } + if (verbose) { + out << format("Syncing with GCP bucket {1}", gcp_bucket) << '\n'; + } + replica->sync_to_gcp(gcp_bucket, gcp_credential_path, encryption_secret, avoid_snapshots); + + } else if (server_url != "") { + if (client_id == "" || encryption_secret == "") { + throw std::string("sync.server.client_id and sync.encryption_secret are required"); + } + if (verbose) { + out << format("Syncing with sync server at {1}", safe_server_url) << '\n'; + } + replica->sync_to_remote(server_url, tc::uuid_from_string(client_id), encryption_secret, + avoid_snapshots); + + } else { + throw std::string("No sync.* settings are configured. See task-sync(5)."); + } + + if (context.config.getBoolean("purge.on-sync")) { + context.tdb2.expire_tasks(); + } + + if (verbose) { + out << "Success!\n"; + // Taskchampion does not provide a measure of the number of operations received from + // the server, but we can give some indication of the number sent. + if (num_local_operations) { + out << format("Sent {1} local operations to the server", num_local_operations) << '\n'; + } + } + + output = out.str(); return status; } diff --git a/src/commands/CmdSync.h b/src/commands/CmdSync.h index b823a04f9..8f0bbbba4 100644 --- a/src/commands/CmdSync.h +++ b/src/commands/CmdSync.h @@ -27,15 +27,15 @@ #ifndef INCLUDED_CMDSYNC #define INCLUDED_CMDSYNC -#include #include #include -class CmdSync : public Command -{ -public: - CmdSync (); - int execute (std::string&); +#include + +class CmdSync : public Command { + public: + CmdSync(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdTags.cpp b/src/commands/CmdTags.cpp index fcf58b631..ee4b6b018 100644 --- a/src/commands/CmdTags.cpp +++ b/src/commands/CmdTags.cpp @@ -25,201 +25,188 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include #include #include #include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdTags::CmdTags () -{ - _keyword = "tags"; - _usage = "task tags"; - _description = "Shows a list of all tags used"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = true; - _accepts_filter = true; +CmdTags::CmdTags() { + _keyword = "tags"; + _usage = "task tags"; + _description = "Shows a list of all tags used"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = true; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::metadata; + _category = Command::Category::metadata; } //////////////////////////////////////////////////////////////////////////////// -int CmdTags::execute (std::string& output) -{ +int CmdTags::execute(std::string& output) { int rc = 0; std::stringstream out; // Get all the tasks. - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); - if (Context::getContext ().config.getBoolean ("list.all.tags")) - for (auto& task : Context::getContext ().tdb2.completed_tasks ()) - tasks.push_back (task); + if (Context::getContext().config.getBoolean("list.all.tags")) + for (auto& task : Context::getContext().tdb2.completed_tasks()) tasks.push_back(task); - int quantity = tasks.size (); + int quantity = tasks.size(); // Apply filter. Filter filter; - std::vector filtered; - filter.subset (tasks, filtered); + std::vector filtered; + filter.subset(tasks, filtered); // Scan all the tasks for their project name, building a map using project // names as keys. - std::map unique; - for (auto& task : filtered) - { - for (auto& tag : task.getTags ()) - if (unique.find (tag) != unique.end ()) + std::map unique; + for (auto& task : filtered) { + for (auto& tag : task.getTags()) + if (unique.find(tag) != unique.end()) unique[tag]++; else unique[tag] = 1; } - if (unique.size ()) - { + if (unique.size()) { // Render a list of tags names from the map. Table view; - view.width (Context::getContext ().getWidth ()); - view.add ("Tag"); - view.add ("Count", false); - setHeaderUnderline (view); + view.width(Context::getContext().getWidth()); + view.add("Tag"); + view.add("Count", false); + setHeaderUnderline(view); Color bold; - if (Context::getContext ().color ()) - bold = Color ("bold"); + if (Context::getContext().color()) bold = Color("bold"); bool special = false; - for (auto& i : unique) - { + for (auto& i : unique) { // Highlight the special tags. - special = (Context::getContext ().color () && - (i.first == "nocolor" || - i.first == "nonag" || - i.first == "nocal" || - i.first == "next")) ? true : false; + special = (Context::getContext().color() && (i.first == "nocolor" || i.first == "nonag" || + i.first == "nocal" || i.first == "next")) + ? true + : false; - int row = view.addRow (); - view.set (row, 0, i.first, special ? bold : Color ()); - view.set (row, 1, i.second, special ? bold : Color ()); + int row = view.addRow(); + view.set(row, 0, i.first, special ? bold : Color()); + view.set(row, 1, i.second, special ? bold : Color()); } - out << optionalBlankLine () - << view.render () - << optionalBlankLine (); + out << optionalBlankLine() << view.render() << optionalBlankLine(); - if (unique.size () == 1) - Context::getContext ().footnote ("1 tag"); + if (unique.size() == 1) + Context::getContext().footnote("1 tag"); else - Context::getContext ().footnote (format ("{1} tags", unique.size ())); + Context::getContext().footnote(format("{1} tags", unique.size())); if (quantity == 1) - Context::getContext ().footnote ("(1 task)"); + Context::getContext().footnote("(1 task)"); else - Context::getContext ().footnote (format ("({1} tasks)", quantity)); + Context::getContext().footnote(format("({1} tasks)", quantity)); out << '\n'; - } - else - { - Context::getContext ().footnote ("No tags."); + } else { + Context::getContext().footnote("No tags."); rc = 1; } - output = out.str (); + output = out.str(); return rc; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionTags::CmdCompletionTags () -{ - _keyword = "_tags"; - _usage = "task _tags"; - _description = "Shows only a list of all tags used, for autocompletion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdCompletionTags::CmdCompletionTags() { + _keyword = "_tags"; + _usage = "task _tags"; + _description = "Shows only a list of all tags used, for autocompletion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionTags::execute (std::string& output) -{ +int CmdCompletionTags::execute(std::string& output) { // Get all the tasks. - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); - if (Context::getContext ().config.getBoolean ("complete.all.tags")) - for (auto& task : Context::getContext ().tdb2.completed_tasks ()) - tasks.push_back (task); + if (Context::getContext().config.getBoolean("complete.all.tags")) + for (auto& task : Context::getContext().tdb2.completed_tasks()) tasks.push_back(task); // Apply filter. Filter filter; - std::vector filtered; - filter.subset (tasks, filtered); + std::vector filtered; + filter.subset(tasks, filtered); // Scan all the tasks for their tags, building a map using tag // names as keys. - std::map unique; + std::map unique; for (auto& task : filtered) - for (auto& tag : task.getTags ()) - unique[tag] = 0; + for (auto& tag : task.getTags()) unique[tag] = 0; // Add built-in tags to map. - unique["nocolor"] = 0; - unique["nonag"] = 0; - unique["nocal"] = 0; - unique["next"] = 0; - unique["ACTIVE"] = 0; + unique["nocolor"] = 0; + unique["nonag"] = 0; + unique["nocal"] = 0; + unique["next"] = 0; + unique["ACTIVE"] = 0; unique["ANNOTATED"] = 0; - unique["BLOCKED"] = 0; - unique["BLOCKING"] = 0; - unique["CHILD"] = 0; // 2017-01-07: Deprecated in 2.6.0 + unique["BLOCKED"] = 0; + unique["BLOCKING"] = 0; + unique["CHILD"] = 0; // 2017-01-07: Deprecated in 2.6.0 unique["COMPLETED"] = 0; - unique["DELETED"] = 0; - unique["DUE"] = 0; - unique["DUETODAY"] = 0; // 2016-03-29: Deprecated in 2.6.0 - unique["INSTANCE"] = 0; - unique["LATEST"] = 0; - unique["MONTH"] = 0; - unique["ORPHAN"] = 0; - unique["OVERDUE"] = 0; - unique["PARENT"] = 0; // 2017-01-07: Deprecated in 2.6.0 - unique["PENDING"] = 0; - unique["PRIORITY"] = 0; - unique["PROJECT"] = 0; - unique["QUARTER"] = 0; - unique["READY"] = 0; + unique["DELETED"] = 0; + unique["DUE"] = 0; + unique["DUETODAY"] = 0; // 2016-03-29: Deprecated in 2.6.0 + unique["INSTANCE"] = 0; + unique["LATEST"] = 0; + unique["MONTH"] = 0; + unique["ORPHAN"] = 0; + unique["OVERDUE"] = 0; + unique["PARENT"] = 0; // 2017-01-07: Deprecated in 2.6.0 + unique["PENDING"] = 0; + unique["PRIORITY"] = 0; + unique["PROJECT"] = 0; + unique["QUARTER"] = 0; + unique["READY"] = 0; unique["SCHEDULED"] = 0; - unique["TAGGED"] = 0; - unique["TEMPLATE"] = 0; - unique["TODAY"] = 0; - unique["TOMORROW"] = 0; - unique["UDA"] = 0; + unique["TAGGED"] = 0; + unique["TEMPLATE"] = 0; + unique["TODAY"] = 0; + unique["TOMORROW"] = 0; + unique["UDA"] = 0; unique["UNBLOCKED"] = 0; - unique["UNTIL"] = 0; - unique["WAITING"] = 0; - unique["WEEK"] = 0; - unique["YEAR"] = 0; + unique["UNTIL"] = 0; + unique["WAITING"] = 0; + unique["WEEK"] = 0; + unique["YEAR"] = 0; unique["YESTERDAY"] = 0; // If you update the above list, update src/commands/CmdInfo.cpp and src/Task.cpp as well. std::stringstream out; - for (auto& it : unique) - out << it.first << '\n'; + for (auto& it : unique) out << it.first << '\n'; - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdTags.h b/src/commands/CmdTags.h index 0e54e7a00..84d2345b6 100644 --- a/src/commands/CmdTags.h +++ b/src/commands/CmdTags.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDTAGS #define INCLUDED_CMDTAGS -#include #include -class CmdTags : public Command -{ -public: - CmdTags (); - int execute (std::string&); +#include + +class CmdTags : public Command { + public: + CmdTags(); + int execute(std::string&); }; -class CmdCompletionTags : public Command -{ -public: - CmdCompletionTags (); - int execute (std::string&); +class CmdCompletionTags : public Command { + public: + CmdCompletionTags(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdTimesheet.cpp b/src/commands/CmdTimesheet.cpp index eee64f007..9ce0bd4e8 100644 --- a/src/commands/CmdTimesheet.cpp +++ b/src/commands/CmdTimesheet.cpp @@ -25,191 +25,171 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include +#include #include #include -#include -#include -#include #include +#include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdTimesheet::CmdTimesheet () -{ - _keyword = "timesheet"; - _usage = "task [filter] timesheet"; - _description = "Summary of completed and started tasks"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdTimesheet::CmdTimesheet() { + _keyword = "timesheet"; + _usage = "task [filter] timesheet"; + _description = "Summary of completed and started tasks"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = true; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::report; + _category = Command::Category::report; } //////////////////////////////////////////////////////////////////////////////// // Whether a the timesheet uses context is defined by the // report.timesheet.context configuration variable. // -bool CmdTimesheet::uses_context () const -{ - auto config = Context::getContext ().config; +bool CmdTimesheet::uses_context() const { + auto config = Context::getContext().config; auto key = "report.timesheet.context"; - if (config.has (key)) - return config.getBoolean (key); + if (config.has(key)) + return config.getBoolean(key); else return _uses_context; } - //////////////////////////////////////////////////////////////////////////////// -int CmdTimesheet::execute (std::string& output) -{ +int CmdTimesheet::execute(std::string& output) { int rc = 0; // Detect a filter. - bool hasFilter {false}; - for (auto& a : Context::getContext ().cli2._args) - { - if (a.hasTag ("FILTER")) - { + bool hasFilter{false}; + for (auto& a : Context::getContext().cli2._args) { + if (a.hasTag("FILTER")) { hasFilter = true; break; } } - if (! hasFilter) - { - auto defaultFilter = Context::getContext ().config.get ("report.timesheet.filter"); + if (!hasFilter) { + auto defaultFilter = Context::getContext().config.get("report.timesheet.filter"); if (defaultFilter == "") defaultFilter = "(+PENDING and start.after:now-4wks) or (+COMPLETED and end.after:now-4wks)"; - Context::getContext ().cli2.addFilter (defaultFilter); + Context::getContext().cli2.addFilter(defaultFilter); } // Apply filter to get a set of tasks. - handleUntil (); - handleRecurrence (); Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); // Subset the tasks to only those that are either completed or started. // The _key attribute is represents either the 'start' or 'end' date. int num_completed = 0; int num_started = 0; - std::vector shown; - for (auto& task : filtered) - { - if (task.getStatus () == Task::completed) - { - task.set ("_key", task.get ("end")); + std::vector shown; + for (auto& task : filtered) { + if (task.getStatus() == Task::completed) { + task.set("_key", task.get("end")); ++num_completed; } - if (task.getStatus () == Task::pending && task.has ("start")) - { - task.set ("_key", task.get ("start")); + if (task.getStatus() == Task::pending && task.has("start")) { + task.set("_key", task.get("start")); ++num_started; } - shown.push_back (task); + shown.push_back(task); } // Sort tasks by _key. - std::sort (shown.begin (), - shown.end (), - [](const Task& a, const Task& b) { return a.get ("_key") < b.get ("_key"); }); + std::sort(shown.begin(), shown.end(), + [](const Task& a, const Task& b) { return a.get("_key") < b.get("_key"); }); // Render the completed table. Table table; - table.width (Context::getContext ().getWidth ()); - if (Context::getContext ().config.getBoolean ("obfuscate")) - table.obfuscate (); - table.add ("Wk"); - table.add ("Date"); - table.add ("Day"); - table.add ("ID"); - table.add ("Action"); - table.add ("Project"); - table.add ("Due"); - table.add ("Task"); - setHeaderUnderline (table); + table.width(Context::getContext().getWidth()); + if (Context::getContext().config.getBoolean("obfuscate")) table.obfuscate(); + table.add("Wk"); + table.add("Date"); + table.add("Day"); + table.add("ID"); + table.add("Action"); + table.add("Project"); + table.add("Due"); + table.add("Task"); + setHeaderUnderline(table); - auto dateformat = Context::getContext ().config.get ("dateformat"); + auto dateformat = Context::getContext().config.get("dateformat"); int previous_week = -1; std::string previous_date = ""; std::string previous_day = ""; int weekCounter = 0; Color week_color; - for (auto& task : shown) - { - Datetime key (task.get_date ("_key")); + for (auto& task : shown) { + Datetime key(task.get_date("_key")); - std::string label = task.has ("end") ? "Completed" - : task.has ("start") ? "Started" - : ""; + std::string label = task.has("end") ? "Completed" : task.has("start") ? "Started" : ""; - auto week = key.week (); - auto date = key.toString (dateformat); - auto due = task.has ("due") ? Datetime (task.get ("due")).toString (dateformat) : ""; - auto day = Datetime::dayNameShort (key.dayOfWeek ()); + auto week = key.week(); + auto date = key.toString(dateformat); + auto due = task.has("due") ? Datetime(task.get("due")).toString(dateformat) : ""; + auto day = Datetime::dayNameShort(key.dayOfWeek()); Color task_color; - autoColorize (task, task_color); + autoColorize(task, task_color); // Add a blank line between weeks. - if (week != previous_week && previous_week != -1) - { - auto row = table.addRowEven (); - table.set (row, 0, " "); + if (week != previous_week && previous_week != -1) { + auto row = table.addRowEven(); + table.set(row, 0, " "); } // Keep track of unique week numbers. - if (week != previous_week) - ++weekCounter; + if (week != previous_week) ++weekCounter; // User-defined oddness. int row; if (weekCounter % 2) - row = table.addRowOdd (); + row = table.addRowOdd(); else - row = table.addRowEven (); + row = table.addRowEven(); // If the data doesn't change, it doesn't get shown. - table.set (row, 0, (week != previous_week ? format ("W{1}", week) : "")); - table.set (row, 1, (date != previous_date ? date : "")); - table.set (row, 2, (day != previous_day ? day : "")); - table.set (row, 3, task.identifier(true)); - table.set (row, 4, label); - table.set (row, 5, task.get ("project")); - table.set (row, 6, due); - table.set (row, 7, task.get ("description"), task_color); + table.set(row, 0, (week != previous_week ? format("W{1}", week) : "")); + table.set(row, 1, (date != previous_date ? date : "")); + table.set(row, 2, (day != previous_day ? day : "")); + table.set(row, 3, task.identifier(true)); + table.set(row, 4, label); + table.set(row, 5, task.get("project")); + table.set(row, 6, due); + table.set(row, 7, task.get("description"), task_color); previous_week = week; previous_date = date; - previous_day = day; + previous_day = day; } // Render the table. std::stringstream out; - if (table.rows ()) - out << optionalBlankLine () - << table.render () - << '\n'; + if (table.rows()) out << optionalBlankLine() << table.render() << '\n'; - if (Context::getContext ().verbose ("affected")) - out << format ("{1} completed, {2} started.", num_completed, num_started) - << '\n'; + if (Context::getContext().verbose("affected")) + out << format("{1} completed, {2} started.", num_completed, num_started) << '\n'; - output = out.str (); + output = out.str(); return rc; } diff --git a/src/commands/CmdTimesheet.h b/src/commands/CmdTimesheet.h index 666e7c830..4a5c96979 100644 --- a/src/commands/CmdTimesheet.h +++ b/src/commands/CmdTimesheet.h @@ -27,15 +27,15 @@ #ifndef INCLUDED_CMDTIMESHEET #define INCLUDED_CMDTIMESHEET -#include #include -class CmdTimesheet : public Command -{ -public: - CmdTimesheet (); - int execute (std::string&); - bool uses_context () const override; +#include + +class CmdTimesheet : public Command { + public: + CmdTimesheet(); + int execute(std::string&) override; + bool uses_context() const override; }; #endif diff --git a/src/commands/CmdUDAs.cpp b/src/commands/CmdUDAs.cpp index d10badcd9..c2fb751a9 100644 --- a/src/commands/CmdUDAs.cpp +++ b/src/commands/CmdUDAs.cpp @@ -25,183 +25,158 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include -#include #include #include -#include +#include +#include #include #include #include -#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdUDAs::CmdUDAs () -{ - _keyword = "udas"; - _usage = "task udas"; - _description = "Shows all the defined UDA details"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdUDAs::CmdUDAs() { + _keyword = "udas"; + _usage = "task udas"; + _description = "Shows all the defined UDA details"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::config; + _category = Command::Category::config; } //////////////////////////////////////////////////////////////////////////////// -int CmdUDAs::execute (std::string& output) -{ +int CmdUDAs::execute(std::string& output) { int rc = 0; std::stringstream out; - std::vector udas; - for (auto& name : Context::getContext ().config) - { - if (name.first.substr (0, 4) == "uda." && - name.first.find (".type") != std::string::npos) - { - auto period = name.first.find ('.', 4); - if (period != std::string::npos) - udas.push_back (name.first.substr (4, period - 4)); + std::vector udas; + for (auto& name : Context::getContext().config) { + if (name.first.substr(0, 4) == "uda." && name.first.find(".type") != std::string::npos) { + auto period = name.first.find('.', 4); + if (period != std::string::npos) udas.push_back(name.first.substr(4, period - 4)); } } // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - if (udas.size ()) - { - std::sort (udas.begin (), udas.end ()); + if (udas.size()) { + std::sort(udas.begin(), udas.end()); // Render a list of UDA name, type, label, allowed values, // possible default value, and finally the usage count. Table table; - table.width (Context::getContext ().getWidth ()); - table.add ("Name"); - table.add ("Type"); - table.add ("Label"); - table.add ("Allowed Values"); - table.add ("Default"); - table.add ("Usage Count"); - setHeaderUnderline (table); + table.width(Context::getContext().getWidth()); + table.add("Name"); + table.add("Type"); + table.add("Label"); + table.add("Allowed Values"); + table.add("Default"); + table.add("Usage Count"); + setHeaderUnderline(table); - for (auto& uda : udas) - { - std::string type = Context::getContext ().config.get ("uda." + uda + ".type"); - std::string label = Context::getContext ().config.get ("uda." + uda + ".label"); - std::string values = Context::getContext ().config.get ("uda." + uda + ".values"); - std::string defval = Context::getContext ().config.get ("uda." + uda + ".default"); - if (label == "") - label = uda; + for (auto& uda : udas) { + std::string type = Context::getContext().config.get("uda." + uda + ".type"); + std::string label = Context::getContext().config.get("uda." + uda + ".label"); + std::string values = Context::getContext().config.get("uda." + uda + ".values"); + std::string defval = Context::getContext().config.get("uda." + uda + ".default"); + if (label == "") label = uda; // Count UDA usage by UDA. int count = 0; for (auto& i : filtered) - if (i.has (uda)) - ++count; + if (i.has(uda)) ++count; - int row = table.addRow (); - table.set (row, 0, uda); - table.set (row, 1, type); - table.set (row, 2, label); - table.set (row, 3, values); - table.set (row, 4, defval); - table.set (row, 5, count); + int row = table.addRow(); + table.set(row, 0, uda); + table.set(row, 1, type); + table.set(row, 2, label); + table.set(row, 3, values); + table.set(row, 4, defval); + table.set(row, 5, count); } - out << optionalBlankLine () - << table.render () - << optionalBlankLine () - << (udas.size () == 1 - ? format ("{1} UDA defined", udas.size ()) - : format ("{1} UDAs defined", udas.size ())) + out << optionalBlankLine() << table.render() << optionalBlankLine() + << (udas.size() == 1 ? format("{1} UDA defined", udas.size()) + : format("{1} UDAs defined", udas.size())) << '\n'; - } - else - { + } else { out << "No UDAs defined.\n"; rc = 1; } // Orphans are task attributes that are not represented in context.columns. - std::map orphans; - for (auto& i : filtered) - { - for (auto& att : i.getUDAOrphans ()) - orphans[att]++; + std::map orphans; + for (auto& i : filtered) { + for (auto& att : i.getUDAOrphans()) orphans[att]++; } - if (orphans.size ()) - { + if (orphans.size()) { // Display the orphans and their counts. Table orphanTable; - orphanTable.width (Context::getContext ().getWidth ()); - orphanTable.add ("Orphan UDA"); - orphanTable.add ("Usage Count"); - setHeaderUnderline (orphanTable); + orphanTable.width(Context::getContext().getWidth()); + orphanTable.add("Orphan UDA"); + orphanTable.add("Usage Count"); + setHeaderUnderline(orphanTable); - for (auto& o : orphans) - { - int row = orphanTable.addRow (); - orphanTable.set (row, 0, o.first); - orphanTable.set (row, 1, o.second); + for (auto& o : orphans) { + int row = orphanTable.addRow(); + orphanTable.set(row, 0, o.first); + orphanTable.set(row, 1, o.second); } - out << optionalBlankLine () - << orphanTable.render () - << optionalBlankLine () - << (udas.size () == 1 - ? format ("{1} Orphan UDA", orphans.size ()) - : format ("{1} Orphan UDAs", orphans.size ())) + out << optionalBlankLine() << orphanTable.render() << optionalBlankLine() + << (udas.size() == 1 ? format("{1} Orphan UDA", orphans.size()) + : format("{1} Orphan UDAs", orphans.size())) << '\n'; } - output = out.str (); + output = out.str(); return rc; } /////////////////////////////////////////////////////////////////////////////// -CmdCompletionUDAs::CmdCompletionUDAs () -{ - _keyword = "_udas"; - _usage = "task _udas"; - _description = "Shows the defined UDAs for completion purposes"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionUDAs::CmdCompletionUDAs() { + _keyword = "_udas"; + _usage = "task _udas"; + _description = "Shows the defined UDAs for completion purposes"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionUDAs::execute (std::string& output) -{ - std::vector udas; - for (auto& name : Context::getContext ().config) - { - if (name.first.substr (0, 4) == "uda." && - name.first.find (".type") != std::string::npos) - { - auto period = name.first.find ('.', 4); - if (period != std::string::npos) - udas.push_back (name.first.substr (4, period - 4)); +int CmdCompletionUDAs::execute(std::string& output) { + std::vector udas; + for (auto& name : Context::getContext().config) { + if (name.first.substr(0, 4) == "uda." && name.first.find(".type") != std::string::npos) { + auto period = name.first.find('.', 4); + if (period != std::string::npos) udas.push_back(name.first.substr(4, period - 4)); } } - if (udas.size ()) - { - std::sort (udas.begin (), udas.end ()); - output = join ("\n", udas) + '\n'; + if (udas.size()) { + std::sort(udas.begin(), udas.end()); + output = join("\n", udas) + '\n'; } return 0; diff --git a/src/commands/CmdUDAs.h b/src/commands/CmdUDAs.h index ac2fb3bc0..185036ecd 100644 --- a/src/commands/CmdUDAs.h +++ b/src/commands/CmdUDAs.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDUDAS #define INCLUDED_CMDUDAS -#include #include -class CmdUDAs : public Command -{ -public: - CmdUDAs (); - int execute (std::string&); +#include + +class CmdUDAs : public Command { + public: + CmdUDAs(); + int execute(std::string&); }; -class CmdCompletionUDAs : public Command -{ -public: - CmdCompletionUDAs (); - int execute (std::string&); +class CmdCompletionUDAs : public Command { + public: + CmdCompletionUDAs(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdUndo.cpp b/src/commands/CmdUndo.cpp index d51d54f97..83156410e 100644 --- a/src/commands/CmdUndo.cpp +++ b/src/commands/CmdUndo.cpp @@ -25,30 +25,123 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include +#include +#include +#include + +#include +#include //////////////////////////////////////////////////////////////////////////////// -CmdUndo::CmdUndo () -{ - _keyword = "undo"; - _usage = "task undo"; - _description = "Reverts the most recent change to a task"; - _read_only = false; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdUndo::CmdUndo() { + _keyword = "undo"; + _usage = "task undo"; + _description = "Reverts the most recent change to a task"; + _read_only = false; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::operation; + _category = Command::Category::operation; } //////////////////////////////////////////////////////////////////////////////// -int CmdUndo::execute (std::string&) -{ - Context::getContext ().tdb2.revert (); +int CmdUndo::execute(std::string&) { + auto& replica = Context::getContext().tdb2.replica(); + rust::Vec undo_ops = replica->get_undo_operations(); + if (confirm_revert(Operation::operations(undo_ops))) { + // Note that commit_reversed_operations rebuilds the working set, so that + // need not be done here. + if (!replica->commit_reversed_operations(std::move(undo_ops))) { + std::cout << "Could not undo: other operations have occurred."; + } + } return 0; } //////////////////////////////////////////////////////////////////////////////// +bool CmdUndo::confirm_revert(const std::vector& undo_ops) { + // Count non-undo operations + int ops_count = 0; + for (auto& op : undo_ops) { + if (!op.is_undo_point()) { + ops_count++; + } + } + if (ops_count == 0) { + std::cout << "No operations to undo.\n"; + return true; + } + + std::cout << "The following " << ops_count << " operations would be reverted:\n"; + + Table view; + if (Context::getContext().config.getBoolean("obfuscate")) view.obfuscate(); + view.width(Context::getContext().getWidth()); + view.add("Uuid"); + view.add("Modification"); + + std::string last_uuid; + std::stringstream mods; + for (auto& op : undo_ops) { + if (op.is_undo_point()) { + continue; + } + + if (last_uuid != op.get_uuid()) { + if (last_uuid.size() != 0) { + int row = view.addRow(); + view.set(row, 0, last_uuid); + view.set(row, 1, mods.str()); + } + last_uuid = op.get_uuid(); + mods = std::stringstream(); + } + + if (op.is_create()) { + mods << "Create task\n"; + } else if (op.is_delete()) { + mods << "Delete (purge) task"; + } else if (op.is_update()) { + auto property = op.get_property(); + auto old_value = op.get_old_value(); + auto value = op.get_value(); + if (Task::isTagAttr(property)) { + if (value && *value == "x") { + mods << "Add tag '" << Task::attr2Tag(property) << "'\n"; + continue; + } else if (!value && old_value && *old_value == "x") { + mods << "Remove tag '" << Task::attr2Tag(property) << "'\n"; + continue; + } + } + if (old_value && value) { + mods << "Update property '" << property << "' from '" << *old_value << "' to '" << *value + << "'\n"; + } else if (old_value) { + mods << "Delete property '" << property << "' (was '" << *old_value << "')\n"; + } else if (value) { + mods << "Add property '" << property << "' with value '" << *value << "'\n"; + } + } + } + int row = view.addRow(); + view.set(row, 0, last_uuid); + view.set(row, 1, mods.str()); + std::cout << view.render() << "\n"; + + return !Context::getContext().config.getBoolean("confirmation") || + confirm( + "The undo command is not reversible. Are you sure you want to revert to the previous " + "state?"); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/commands/CmdUndo.h b/src/commands/CmdUndo.h index ca2a0dce1..590184178 100644 --- a/src/commands/CmdUndo.h +++ b/src/commands/CmdUndo.h @@ -27,14 +27,19 @@ #ifndef INCLUDED_CMDUNDO #define INCLUDED_CMDUNDO -#include #include +#include -class CmdUndo : public Command -{ -public: - CmdUndo (); - int execute (std::string&); +#include +#include + +class CmdUndo : public Command { + public: + CmdUndo(); + int execute(std::string &); + + private: + bool confirm_revert(const std::vector &); }; #endif diff --git a/src/commands/CmdUnique.cpp b/src/commands/CmdUnique.cpp index cda54f196..4b24c0043 100644 --- a/src/commands/CmdUnique.cpp +++ b/src/commands/CmdUnique.cpp @@ -25,72 +25,67 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include #include #include +#include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdUnique::CmdUnique () -{ - _keyword = "_unique"; - _usage = "task _unique "; - _description = "Generates lists of unique attribute values"; - _read_only = true; - _displays_id = true; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdUnique::CmdUnique() { + _keyword = "_unique"; + _usage = "task _unique "; + _description = "Generates lists of unique attribute values"; + _read_only = true; + _displays_id = true; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = true; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdUnique::execute (std::string& output) -{ +int CmdUnique::execute(std::string& output) { // Apply filter. Filter filter; - filter.disableSafety (); - std::vector filtered; - filter.subset (filtered); + filter.disableSafety(); + std::vector filtered; + filter.subset(filtered); // Find . - std::string attribute {}; + std::string attribute{}; // Just the first arg. - auto words = Context::getContext ().cli2.getWords (); - if (words.size () == 0) - throw std::string ("An attribute must be specified. See 'task _columns'."); + auto words = Context::getContext().cli2.getWords(); + if (words.size() == 0) throw std::string("An attribute must be specified. See 'task _columns'."); attribute = words[0]; std::string canonical; - if (! Context::getContext ().cli2.canonicalize (canonical, "attribute", attribute)) - throw std::string ("You must specify an attribute or UDA."); + if (!Context::getContext().cli2.canonicalize(canonical, "attribute", attribute)) + throw std::string("You must specify an attribute or UDA."); // Find the unique set of matching tasks. - std::set values; - for (auto& task : filtered) - { - if (task.has (canonical)) - { - values.insert (task.get (canonical)); - } - else if (canonical == "id" && - task.getStatus () != Task::deleted && - task.getStatus () != Task::completed) - { - values.insert (format (task.id)); + std::set values; + for (auto& task : filtered) { + if (task.has(canonical)) { + values.insert(task.get(canonical)); + } else if (canonical == "id" && task.getStatus() != Task::deleted && + task.getStatus() != Task::completed) { + values.insert(format(task.id)); } } // Generate list of values. - for (auto& value : values) - output += value + '\n'; + for (auto& value : values) output += value + '\n'; - Context::getContext ().headers.clear (); + Context::getContext().headers.clear(); return 0; } diff --git a/src/commands/CmdUnique.h b/src/commands/CmdUnique.h index cbe4b9d61..cad5f8405 100644 --- a/src/commands/CmdUnique.h +++ b/src/commands/CmdUnique.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDUNIQUE #define INCLUDED_CMDUNIQUE -#include #include -class CmdUnique : public Command -{ -public: - CmdUnique (); - int execute (std::string&); +#include + +class CmdUnique : public Command { + public: + CmdUnique(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdUrgency.cpp b/src/commands/CmdUrgency.cpp index 55487e507..40731c81c 100644 --- a/src/commands/CmdUrgency.cpp +++ b/src/commands/CmdUrgency.cpp @@ -25,56 +25,53 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include #include #include -#include #include +#include + //////////////////////////////////////////////////////////////////////////////// -CmdUrgency::CmdUrgency () -{ - _keyword = "_urgency"; - _usage = "task _urgency"; - _description = "Displays the urgency measure of a task"; - _read_only = true; - _displays_id = false; - _needs_gc = true; - _uses_context = false; - _accepts_filter = true; +CmdUrgency::CmdUrgency() { + _keyword = "_urgency"; + _usage = "task _urgency"; + _description = "Displays the urgency measure of a task"; + _read_only = true; + _displays_id = false; + _needs_gc = true; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = true; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdUrgency::execute (std::string& output) -{ +int CmdUrgency::execute(std::string& output) { // Apply filter. Filter filter; - std::vector filtered; - filter.subset (filtered); + std::vector filtered; + filter.subset(filtered); - if (filtered.size () == 0) - { - Context::getContext ().footnote ("No tasks specified."); + if (filtered.size() == 0) { + Context::getContext().footnote("No tasks specified."); return 1; } // Display urgency for the selected tasks. std::stringstream out; - for (auto& task : filtered) - { - out << format ("task {1} urgency {2}", - task.identifier (), - Lexer::trim (format (task.urgency (), 6, 3))) + for (auto& task : filtered) { + out << format("task {1} urgency {2}", task.identifier(), + Lexer::trim(format(task.urgency(), 6, 3))) << '\n'; } - output = out.str (); + output = out.str(); return 0; } diff --git a/src/commands/CmdUrgency.h b/src/commands/CmdUrgency.h index aeaad047d..b2be3256e 100644 --- a/src/commands/CmdUrgency.h +++ b/src/commands/CmdUrgency.h @@ -27,14 +27,14 @@ #ifndef INCLUDED_CMDURGENCY #define INCLUDED_CMDURGENCY -#include #include -class CmdUrgency : public Command -{ -public: - CmdUrgency (); - int execute (std::string&); +#include + +class CmdUrgency : public Command { + public: + CmdUrgency(); + int execute(std::string&); }; #endif diff --git a/src/commands/CmdVersion.cpp b/src/commands/CmdVersion.cpp index 0797c02f3..21bce985a 100644 --- a/src/commands/CmdVersion.cpp +++ b/src/commands/CmdVersion.cpp @@ -25,95 +25,94 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include -#include -#include #include +#include #include + +#include #ifdef HAVE_COMMIT #include #endif -#include #include +#include //////////////////////////////////////////////////////////////////////////////// -CmdVersion::CmdVersion () -{ - _keyword = "version"; - _usage = "task version"; - _description = "Shows the Taskwarrior version number"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdVersion::CmdVersion() { + _keyword = "version"; + _usage = "task version"; + _description = "Shows the Taskwarrior version number"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::misc; + _category = Command::Category::misc; } //////////////////////////////////////////////////////////////////////////////// -int CmdVersion::execute (std::string& output) -{ +int CmdVersion::execute(std::string& output) { std::stringstream out; // Create a table for the disclaimer. - int width = Context::getContext ().getWidth (); + int width = Context::getContext().getWidth(); Table disclaimer; - disclaimer.width (width); - disclaimer.add (""); - disclaimer.set (disclaimer.addRow (), 0, "Taskwarrior may be copied only under the terms of the MIT license, which may be found in the Taskwarrior source kit."); + disclaimer.width(width); + disclaimer.add(""); + disclaimer.set(disclaimer.addRow(), 0, + "Taskwarrior may be copied only under the terms of the MIT license, which may be " + "found in the Taskwarrior source kit."); // Create a table for the URL. Table link; - link.width (width); - link.add (""); - link.set (link.addRow (), 0, "Documentation for Taskwarrior can be found using 'man task', 'man taskrc', 'man task-color', 'man task-sync' or at https://taskwarrior.org"); + link.width(width); + link.add(""); + link.set(link.addRow(), 0, + "Documentation for Taskwarrior can be found using 'man task', 'man taskrc', 'man " + "task-color', 'man task-sync' or at https://taskwarrior.org"); + + Datetime now; Color bold; - if (Context::getContext ().color ()) - bold = Color ("bold"); + if (Context::getContext().color()) bold = Color("bold"); out << '\n' - << format ("{1} {2} built for ", bold.colorize (PACKAGE), bold.colorize (VERSION)) - << osName () + << format("{1} {2} built for ", bold.colorize(PACKAGE), bold.colorize(VERSION)) << osName() << '\n' - << "Copyright (C) 2006 - 2021 T. Babej, P. Beckingham, F. Hernandez." + << "Copyright (C) 2006 - " << now.year() << " T. Babej, P. Beckingham, F. Hernandez." << '\n' << '\n' - << '\n' - << disclaimer.render () - << '\n' - << link.render () - << '\n'; + << disclaimer.render() << '\n' + << link.render() << '\n'; - output = out.str (); + output = out.str(); return 0; } //////////////////////////////////////////////////////////////////////////////// -CmdCompletionVersion::CmdCompletionVersion () -{ - _keyword = "_version"; - _usage = "task _version"; - _description = "Shows only the Taskwarrior version number"; - _read_only = true; - _displays_id = false; - _needs_gc = false; - _uses_context = false; - _accepts_filter = false; +CmdCompletionVersion::CmdCompletionVersion() { + _keyword = "_version"; + _usage = "task _version"; + _description = "Shows only the Taskwarrior version number"; + _read_only = true; + _displays_id = false; + _needs_gc = false; + _needs_recur_update = false; + _uses_context = false; + _accepts_filter = false; _accepts_modifications = false; _accepts_miscellaneous = false; - _category = Command::Category::internal; + _category = Command::Category::internal; } //////////////////////////////////////////////////////////////////////////////// -int CmdCompletionVersion::execute (std::string& output) -{ +int CmdCompletionVersion::execute(std::string& output) { #ifdef HAVE_COMMIT - output = std::string (VERSION) - + std::string (" (") - + std::string (COMMIT) - + std::string (")"); + output = std::string(VERSION) + std::string(" (") + std::string(COMMIT) + std::string(")"); #else output = VERSION; #endif diff --git a/src/commands/CmdVersion.h b/src/commands/CmdVersion.h index 9a1ef26e2..1c7e6233e 100644 --- a/src/commands/CmdVersion.h +++ b/src/commands/CmdVersion.h @@ -27,21 +27,20 @@ #ifndef INCLUDED_CMDVERSION #define INCLUDED_CMDVERSION -#include #include -class CmdVersion : public Command -{ -public: - CmdVersion (); - int execute (std::string&); +#include + +class CmdVersion : public Command { + public: + CmdVersion(); + int execute(std::string&); }; -class CmdCompletionVersion : public Command -{ -public: - CmdCompletionVersion (); - int execute (std::string&); +class CmdCompletionVersion : public Command { + public: + CmdCompletionVersion(); + int execute(std::string&); }; #endif diff --git a/src/commands/Command.cpp b/src/commands/Command.cpp index 15b311aae..3e52a4389 100644 --- a/src/commands/Command.cpp +++ b/src/commands/Command.cpp @@ -25,14 +25,7 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first #include #include @@ -55,6 +48,13 @@ #include #include #include +#include +#include +#include +#include + +#include +#include #ifdef HAVE_EXECUTE #include #endif @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -86,222 +87,257 @@ #include #include #include - -#include -#include #include +#include +#include //////////////////////////////////////////////////////////////////////////////// -void Command::factory (std::map & all) -{ +void Command::factory(std::map& all) { Command* c; - c = new CmdAdd (); all[c->keyword ()] = c; - c = new CmdAnnotate (); all[c->keyword ()] = c; - c = new CmdAppend (); all[c->keyword ()] = c; - c = new CmdBurndownDaily (); all[c->keyword ()] = c; - c = new CmdBurndownMonthly (); all[c->keyword ()] = c; - c = new CmdBurndownWeekly (); all[c->keyword ()] = c; - c = new CmdCalc (); all[c->keyword ()] = c; - c = new CmdCalendar (); all[c->keyword ()] = c; - c = new CmdColor (); all[c->keyword ()] = c; - c = new CmdColumns (); all[c->keyword ()] = c; - c = new CmdCommands (); all[c->keyword ()] = c; - c = new CmdCompletionAliases (); all[c->keyword ()] = c; - c = new CmdCompletionColumns (); all[c->keyword ()] = c; - c = new CmdCompletionCommands (); all[c->keyword ()] = c; - c = new CmdCompletionConfig (); all[c->keyword ()] = c; - c = new CmdCompletionContext (); all[c->keyword ()] = c; - c = new CmdCompletionIds (); all[c->keyword ()] = c; - c = new CmdCompletionUDAs (); all[c->keyword ()] = c; - c = new CmdCompletionUuids (); all[c->keyword ()] = c; - c = new CmdCompletionProjects (); all[c->keyword ()] = c; - c = new CmdCompletionTags (); all[c->keyword ()] = c; - c = new CmdCompletionVersion (); all[c->keyword ()] = c; - c = new CmdConfig (); all[c->keyword ()] = c; - c = new CmdContext (); all[c->keyword ()] = c; - c = new CmdCount (); all[c->keyword ()] = c; - c = new CmdDelete (); all[c->keyword ()] = c; - c = new CmdDenotate (); all[c->keyword ()] = c; - c = new CmdDiagnostics (); all[c->keyword ()] = c; - c = new CmdDone (); all[c->keyword ()] = c; - c = new CmdDuplicate (); all[c->keyword ()] = c; - c = new CmdEdit (); all[c->keyword ()] = c; + c = new CmdAdd(); + all[c->keyword()] = c; + c = new CmdAnnotate(); + all[c->keyword()] = c; + c = new CmdAppend(); + all[c->keyword()] = c; + c = new CmdBurndownDaily(); + all[c->keyword()] = c; + c = new CmdBurndownMonthly(); + all[c->keyword()] = c; + c = new CmdBurndownWeekly(); + all[c->keyword()] = c; + c = new CmdCalc(); + all[c->keyword()] = c; + c = new CmdCalendar(); + all[c->keyword()] = c; + c = new CmdColor(); + all[c->keyword()] = c; + c = new CmdColumns(); + all[c->keyword()] = c; + c = new CmdCommands(); + all[c->keyword()] = c; + c = new CmdCompletionAliases(); + all[c->keyword()] = c; + c = new CmdCompletionColumns(); + all[c->keyword()] = c; + c = new CmdCompletionCommands(); + all[c->keyword()] = c; + c = new CmdCompletionConfig(); + all[c->keyword()] = c; + c = new CmdCompletionContext(); + all[c->keyword()] = c; + c = new CmdCompletionIds(); + all[c->keyword()] = c; + c = new CmdCompletionUDAs(); + all[c->keyword()] = c; + c = new CmdCompletionUuids(); + all[c->keyword()] = c; + c = new CmdCompletionProjects(); + all[c->keyword()] = c; + c = new CmdCompletionTags(); + all[c->keyword()] = c; + c = new CmdCompletionVersion(); + all[c->keyword()] = c; + c = new CmdConfig(); + all[c->keyword()] = c; + c = new CmdContext(); + all[c->keyword()] = c; + c = new CmdCount(); + all[c->keyword()] = c; + c = new CmdDelete(); + all[c->keyword()] = c; + c = new CmdDenotate(); + all[c->keyword()] = c; + c = new CmdDiagnostics(); + all[c->keyword()] = c; + c = new CmdDone(); + all[c->keyword()] = c; + c = new CmdDuplicate(); + all[c->keyword()] = c; + c = new CmdEdit(); + all[c->keyword()] = c; #ifdef HAVE_EXECUTE - c = new CmdExec (); all[c->keyword ()] = c; + c = new CmdExec(); + all[c->keyword()] = c; #endif - c = new CmdExport (); all[c->keyword ()] = c; - c = new CmdGet (); all[c->keyword ()] = c; - c = new CmdGHistoryDaily (); all[c->keyword ()] = c; - c = new CmdGHistoryWeekly (); all[c->keyword ()] = c; - c = new CmdGHistoryMonthly (); all[c->keyword ()] = c; - c = new CmdGHistoryAnnual (); all[c->keyword ()] = c; - c = new CmdHelp (); all[c->keyword ()] = c; - c = new CmdHistoryDaily (); all[c->keyword ()] = c; - c = new CmdHistoryWeekly (); all[c->keyword ()] = c; - c = new CmdHistoryMonthly (); all[c->keyword ()] = c; - c = new CmdHistoryAnnual (); all[c->keyword ()] = c; - c = new CmdIDs (); all[c->keyword ()] = c; - c = new CmdImport (); all[c->keyword ()] = c; - c = new CmdInfo (); all[c->keyword ()] = c; - c = new CmdLog (); all[c->keyword ()] = c; - c = new CmdLogo (); all[c->keyword ()] = c; - c = new CmdModify (); all[c->keyword ()] = c; - c = new CmdNews (); all[c->keyword ()] = c; - c = new CmdPrepend (); all[c->keyword ()] = c; - c = new CmdProjects (); all[c->keyword ()] = c; - c = new CmdPurge (); all[c->keyword ()] = c; - c = new CmdReports (); all[c->keyword ()] = c; - c = new CmdShow (); all[c->keyword ()] = c; - c = new CmdShowRaw (); all[c->keyword ()] = c; - c = new CmdStart (); all[c->keyword ()] = c; - c = new CmdStats (); all[c->keyword ()] = c; - c = new CmdStop (); all[c->keyword ()] = c; - c = new CmdSummary (); all[c->keyword ()] = c; - c = new CmdSync (); all[c->keyword ()] = c; - c = new CmdTags (); all[c->keyword ()] = c; - c = new CmdTimesheet (); all[c->keyword ()] = c; - c = new CmdUDAs (); all[c->keyword ()] = c; - c = new CmdUndo (); all[c->keyword ()] = c; - c = new CmdUnique (); all[c->keyword ()] = c; - c = new CmdUrgency (); all[c->keyword ()] = c; - c = new CmdUUIDs (); all[c->keyword ()] = c; - c = new CmdVersion (); all[c->keyword ()] = c; - c = new CmdZshAttributes (); all[c->keyword ()] = c; - c = new CmdZshCommands (); all[c->keyword ()] = c; - c = new CmdZshCompletionIds (); all[c->keyword ()] = c; - c = new CmdZshCompletionUuids (); all[c->keyword ()] = c; + c = new CmdExport(); + all[c->keyword()] = c; + c = new CmdGet(); + all[c->keyword()] = c; + c = new CmdGHistoryDaily(); + all[c->keyword()] = c; + c = new CmdGHistoryWeekly(); + all[c->keyword()] = c; + c = new CmdGHistoryMonthly(); + all[c->keyword()] = c; + c = new CmdGHistoryAnnual(); + all[c->keyword()] = c; + c = new CmdHelp(); + all[c->keyword()] = c; + c = new CmdHistoryDaily(); + all[c->keyword()] = c; + c = new CmdHistoryWeekly(); + all[c->keyword()] = c; + c = new CmdHistoryMonthly(); + all[c->keyword()] = c; + c = new CmdHistoryAnnual(); + all[c->keyword()] = c; + c = new CmdIDs(); + all[c->keyword()] = c; + c = new CmdImport(); + all[c->keyword()] = c; + c = new CmdImportV2(); + all[c->keyword()] = c; + c = new CmdInfo(); + all[c->keyword()] = c; + c = new CmdLog(); + all[c->keyword()] = c; + c = new CmdLogo(); + all[c->keyword()] = c; + c = new CmdModify(); + all[c->keyword()] = c; + c = new CmdNews(); + all[c->keyword()] = c; + c = new CmdPrepend(); + all[c->keyword()] = c; + c = new CmdProjects(); + all[c->keyword()] = c; + c = new CmdPurge(); + all[c->keyword()] = c; + c = new CmdReports(); + all[c->keyword()] = c; + c = new CmdShow(); + all[c->keyword()] = c; + c = new CmdShowRaw(); + all[c->keyword()] = c; + c = new CmdStart(); + all[c->keyword()] = c; + c = new CmdStats(); + all[c->keyword()] = c; + c = new CmdStop(); + all[c->keyword()] = c; + c = new CmdSummary(); + all[c->keyword()] = c; + c = new CmdSync(); + all[c->keyword()] = c; + c = new CmdTags(); + all[c->keyword()] = c; + c = new CmdTimesheet(); + all[c->keyword()] = c; + c = new CmdUDAs(); + all[c->keyword()] = c; + c = new CmdUndo(); + all[c->keyword()] = c; + c = new CmdUnique(); + all[c->keyword()] = c; + c = new CmdUrgency(); + all[c->keyword()] = c; + c = new CmdUUIDs(); + all[c->keyword()] = c; + c = new CmdVersion(); + all[c->keyword()] = c; + c = new CmdZshAttributes(); + all[c->keyword()] = c; + c = new CmdZshCommands(); + all[c->keyword()] = c; + c = new CmdZshCompletionIds(); + all[c->keyword()] = c; + c = new CmdZshCompletionUuids(); + all[c->keyword()] = c; // Instantiate a command object for each custom report. - std::vector reports; - for (auto &i : Context::getContext ().config) - { - if (i.first.substr (0, 7) == "report.") - { - std::string report = i.first.substr (7); - auto columns = report.find (".columns"); - if (columns != std::string::npos) - reports.push_back (report.substr (0, columns)); + std::vector reports; + for (auto& i : Context::getContext().config) { + if (i.first.substr(0, 7) == "report.") { + std::string report = i.first.substr(7); + auto columns = report.find(".columns"); + if (columns != std::string::npos) reports.push_back(report.substr(0, columns)); } } - for (auto &report : reports) - { + for (auto& report : reports) { // Make sure a custom report does not clash with a built-in command. - if (all.find (report) != all.end ()) - throw format ("Custom report '{1}' conflicts with built-in task command.", report); + if (all.find(report) != all.end()) + throw format("Custom report '{1}' conflicts with built-in task command.", report); - c = new CmdCustom ( - report, - "task " + report, - Context::getContext ().config.get ("report." + report + ".description")); + c = new CmdCustom(report, "task " + report, + Context::getContext().config.get("report." + report + ".description")); - all[c->keyword ()] = c; + all[c->keyword()] = c; } } //////////////////////////////////////////////////////////////////////////////// -const std::map Command::categoryNames = -{ - // These strings are intentionally not l10n'd: they are used as identifiers. - {Command::Category::unassigned, "unassigned"} // should never happen - ,{Command::Category::metadata, "metadata"} - ,{Command::Category::report, "report"} - ,{Command::Category::operation, "operation"} - ,{Command::Category::context, "context"} - ,{Command::Category::graphs, "graphs" } - ,{Command::Category::config, "config" } - ,{Command::Category::migration, "migration"} - ,{Command::Category::misc, "misc" } - ,{Command::Category::internal, "internal"} - ,{Command::Category::UNDOCUMENTED, "undocumented"} -}; +const std::map Command::categoryNames = { + // These strings are intentionally not l10n'd: they are used as identifiers. + {Command::Category::unassigned, "unassigned"} // should never happen + , + {Command::Category::metadata, "metadata"}, + {Command::Category::report, "report"}, + {Command::Category::operation, "operation"}, + {Command::Category::context, "context"}, + {Command::Category::graphs, "graphs"}, + {Command::Category::config, "config"}, + {Command::Category::migration, "migration"}, + {Command::Category::misc, "misc"}, + {Command::Category::internal, "internal"}, + {Command::Category::UNDOCUMENTED, "undocumented"}}; //////////////////////////////////////////////////////////////////////////////// -Command::Command () -: _keyword ("") -, _usage ("") -, _description ("") -, _read_only (true) -, _displays_id (true) -, _needs_confirm (false) -, _needs_gc (true) -, _uses_context (false) -, _accepts_filter (false) -, _accepts_modifications (false) -, _accepts_miscellaneous (false) -, _category(Category::unassigned) -, _permission_quit (false) -, _permission_all (false) -, _first_iteration (true) -{ -} +Command::Command() + : _keyword(""), + _usage(""), + _description(""), + _read_only(true), + _displays_id(true), + _needs_confirm(false), + _needs_gc(true), + _needs_recur_update(false), + _uses_context(false), + _accepts_filter(false), + _accepts_modifications(false), + _accepts_miscellaneous(false), + _category(Category::unassigned), + _permission_quit(false), + _permission_all(false), + _first_iteration(true) {} //////////////////////////////////////////////////////////////////////////////// -std::string Command::keyword () const -{ - return _keyword; -} +std::string Command::keyword() const { return _keyword; } //////////////////////////////////////////////////////////////////////////////// -std::string Command::usage () const -{ - return _usage; -} +std::string Command::usage() const { return _usage; } //////////////////////////////////////////////////////////////////////////////// -std::string Command::description () const -{ - return _description; -} +std::string Command::description() const { return _description; } //////////////////////////////////////////////////////////////////////////////// -bool Command::read_only () const -{ - return _read_only; -} +bool Command::read_only() const { return _read_only; } //////////////////////////////////////////////////////////////////////////////// -bool Command::displays_id () const -{ - return _displays_id; -} +bool Command::displays_id() const { return _displays_id; } //////////////////////////////////////////////////////////////////////////////// -bool Command::needs_gc () const -{ - return _needs_gc; -} +bool Command::needs_gc() const { return _needs_gc; } //////////////////////////////////////////////////////////////////////////////// -bool Command::uses_context () const -{ - return _uses_context; -} +bool Command::needs_recur_update() const { return _needs_recur_update; } //////////////////////////////////////////////////////////////////////////////// -bool Command::accepts_filter () const -{ - return _accepts_filter; -} +bool Command::uses_context() const { return _uses_context; } //////////////////////////////////////////////////////////////////////////////// -bool Command::accepts_modifications () const -{ - return _accepts_modifications; -} +bool Command::accepts_filter() const { return _accepts_filter; } //////////////////////////////////////////////////////////////////////////////// -bool Command::accepts_miscellaneous () const -{ - return _accepts_miscellaneous; -} +bool Command::accepts_modifications() const { return _accepts_modifications; } //////////////////////////////////////////////////////////////////////////////// -Command::Category Command::category () const -{ - return _category; -} +bool Command::accepts_miscellaneous() const { return _accepts_miscellaneous; } + +//////////////////////////////////////////////////////////////////////////////// +Command::Category Command::category() const { return _category; } //////////////////////////////////////////////////////////////////////////////// // Returns true or false indicating whether to proceed with a write command, on @@ -312,51 +348,44 @@ Command::Category Command::category () const // rc.bulk // rc.confirmation // this->_read_only -bool Command::permission ( - const std::string& question, - unsigned int quantity) -{ +bool Command::permission(const std::string& question, unsigned int quantity) { // Read-only commands do not need to seek permission. Write commands are // granted permission automatically if the 'all' selection was made in an // earlier call. Or if the 'all' option has already been made. - if (_read_only || - _permission_all) - return true; + if (_read_only || _permission_all) return true; // If the 'quit' selection has already been made. - if (_permission_quit) - return false; + if (_permission_quit) return false; // What remains are write commands that have not yet selected 'all' or 'quit'. // Describe the task. - bool confirmation = Context::getContext ().config.getBoolean ("confirmation"); - unsigned int bulk = Context::getContext ().config.getInteger ("bulk"); + bool confirmation = Context::getContext().config.getBoolean("confirmation"); + unsigned int bulk = Context::getContext().config.getInteger("bulk"); // Quantity 1 modifications have optional confirmation, and only (y/n). - if (quantity == 1) - { - if (!_needs_confirm || - !confirmation) - return true; + if (quantity == 1) { + if (!_needs_confirm || !confirmation) return true; - bool answer = confirm (question); + bool answer = confirm(question); return answer; } // 1 < Quantity < bulk modifications have optional confirmation, in the (y/n/a/q) // style. Bulk = 0 denotes infinite bulk. - if ((bulk == 0 || quantity < bulk) && (!_needs_confirm || !confirmation)) - return true; + if ((bulk == 0 || quantity < bulk) && (!_needs_confirm || !confirmation)) return true; - if (Context::getContext ().verbose ("blank") && !_first_iteration) - std::cout << '\n'; - int answer = confirm4 (question); + if (Context::getContext().verbose("blank") && !_first_iteration) std::cout << '\n'; + int answer = confirm4(question); _first_iteration = false; - switch (answer) - { - case 1: return true; // yes - case 2: _permission_all = true; return true; // all - case 3: _permission_quit = true; return false; // quit + switch (answer) { + case 1: + return true; // yes + case 2: + _permission_all = true; + return true; // all + case 3: + _permission_quit = true; + return false; // quit } return false; // This line keeps the compiler happy. diff --git a/src/commands/Command.h b/src/commands/Command.h index d93dd90c3..6292160be 100644 --- a/src/commands/Command.h +++ b/src/commands/Command.h @@ -27,16 +27,14 @@ #ifndef INCLUDED_COMMAND #define INCLUDED_COMMAND -#include -#include -#include #include -class Command -{ -public: - enum class Category - { +#include +#include + +class Command { + public: + enum class Category { unassigned, // In presentation ("usefulness") order: frequently-used categories first. metadata, @@ -52,46 +50,48 @@ public: // Whenever you extend this enum, update categoryNames. }; - Command (); - virtual ~Command () = default; + Command(); + virtual ~Command() = default; - static void factory (std::map &); + static void factory(std::map&); - std::string keyword () const; - std::string usage () const; - std::string description () const; - bool read_only () const; - bool displays_id () const; - bool needs_gc () const; - virtual bool uses_context () const; - bool accepts_filter () const; - bool accepts_modifications () const; - bool accepts_miscellaneous () const; - Category category () const; - virtual int execute (std::string&) = 0; + std::string keyword() const; + std::string usage() const; + std::string description() const; + bool read_only() const; + bool displays_id() const; + bool needs_gc() const; + bool needs_recur_update() const; + virtual bool uses_context() const; + bool accepts_filter() const; + bool accepts_modifications() const; + bool accepts_miscellaneous() const; + Category category() const; + virtual int execute(std::string&) = 0; -protected: - bool permission (const std::string&, unsigned int); - static const std::map categoryNames; + protected: + bool permission(const std::string&, unsigned int); + static const std::map categoryNames; -protected: + protected: std::string _keyword; std::string _usage; std::string _description; - bool _read_only; - bool _displays_id; - bool _needs_confirm; - bool _needs_gc; - bool _uses_context; - bool _accepts_filter; - bool _accepts_modifications; - bool _accepts_miscellaneous; - Category _category; + bool _read_only; + bool _displays_id; + bool _needs_confirm; + bool _needs_gc; + bool _needs_recur_update; + bool _uses_context; + bool _accepts_filter; + bool _accepts_modifications; + bool _accepts_miscellaneous; + Category _category; // Permission support - bool _permission_quit; - bool _permission_all; - bool _first_iteration; + bool _permission_quit; + bool _permission_all; + bool _first_iteration; }; #endif diff --git a/src/dependency.cpp b/src/dependency.cpp index 154859e32..380c471cb 100644 --- a/src/dependency.cpp +++ b/src/dependency.cpp @@ -25,62 +25,56 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include +// cmake.h include header must come first + #include +#include #include #include -#include -#define STRING_DEPEND_BLOCKED "Task {1} is blocked by:" +#include +#include + +#define STRING_DEPEND_BLOCKED "Task {1} is blocked by:" //////////////////////////////////////////////////////////////////////////////// // Returns true if the supplied task adds a cycle to the dependency chain. -bool dependencyIsCircular (const Task& task) -{ +bool dependencyIsCircular(const Task& task) { // A new task has no UUID assigned yet, and therefore cannot be part of any // dependency chain. - if (task.has ("uuid")) - { - auto task_uuid = task.get ("uuid"); + if (task.has("uuid")) { + auto task_uuid = task.get("uuid"); - std::stack s; - s.push (task); + std::stack s; + s.push(task); - std::unordered_set visited; - visited.insert (task_uuid); + std::unordered_set visited; + visited.insert(task_uuid); - while (! s.empty ()) - { - Task& current = s.top (); - auto deps_current = current.getDependencyUUIDs (); + while (!s.empty()) { + Task& current = s.top(); + auto deps_current = current.getDependencyUUIDs(); // This is a basic depth first search that always terminates given the // fact that we do not visit any task twice - for (const auto& dep : deps_current) - { - if (Context::getContext ().tdb2.get (dep, current)) - { - auto current_uuid = current.get ("uuid"); + for (const auto& dep : deps_current) { + if (Context::getContext().tdb2.get(dep, current)) { + auto current_uuid = current.get("uuid"); - if (task_uuid == current_uuid) - { + if (task_uuid == current_uuid) { // Cycle found, initial task reached for the second time! return true; } - if (visited.find (current_uuid) == visited.end ()) - { + if (visited.find(current_uuid) == visited.end()) { // Push the task to the stack, if it has not been processed yet - s.push (current); - visited.insert (current_uuid); + s.push(current); + visited.insert(current_uuid); } } } - s.pop (); + s.pop(); } } @@ -116,76 +110,61 @@ bool dependencyIsCircular (const Task& task) // 1 dep:3,5 // 4 dep:3,5 // -void dependencyChainOnComplete (Task& task) -{ - auto blocking = task.getDependencyTasks (); +void dependencyChainOnComplete(Task& task) { + auto blocking = task.getDependencyTasks(); // If the task is anything but the tail end of a dependency chain. - if (blocking.size ()) - { - auto blocked = task.getBlockedTasks (); + if (blocking.size()) { + auto blocked = task.getBlockedTasks(); // Nag about broken chain. - if (Context::getContext ().config.getBoolean ("dependency.reminder")) - { - std::cout << format (STRING_DEPEND_BLOCKED, task.identifier ()) - << '\n'; + if (Context::getContext().config.getBoolean("dependency.reminder")) { + std::cout << format(STRING_DEPEND_BLOCKED, task.identifier()) << '\n'; for (const auto& b : blocking) - std::cout << " " << b.id << ' ' << b.get ("description") << '\n'; + std::cout << " " << b.id << ' ' << b.get("description") << '\n'; } // If there are both blocking and blocked tasks, the chain is broken. - if (blocked.size ()) - { - if (Context::getContext ().config.getBoolean ("dependency.reminder")) - { + if (blocked.size()) { + if (Context::getContext().config.getBoolean("dependency.reminder")) { std::cout << "and is blocking:\n"; for (const auto& b : blocked) - std::cout << " " << b.id << ' ' << b.get ("description") << '\n'; + std::cout << " " << b.id << ' ' << b.get("description") << '\n'; } - if (!Context::getContext ().config.getBoolean ("dependency.confirmation") || - confirm ("Would you like the dependency chain fixed?")) - { + if (!Context::getContext().config.getBoolean("dependency.confirmation") || + confirm("Would you like the dependency chain fixed?")) { // Repair the chain - everything in blocked should now depend on // everything in blocking, instead of task.id. - for (auto& left : blocked) - { - left.removeDependency (task.id); + for (auto& left : blocked) { + left.removeDependency(task.id); - for (const auto& right : blocking) - left.addDependency (right.id); + for (const auto& right : blocking) left.addDependency(right.id); } // Now update TDB2, now that the updates have all occurred. - for (auto& left : blocked) - Context::getContext ().tdb2.modify (left); + for (auto& left : blocked) Context::getContext().tdb2.modify(left); - for (auto& right : blocking) - Context::getContext ().tdb2.modify (right); + for (auto& right : blocking) Context::getContext().tdb2.modify(right); } } } } //////////////////////////////////////////////////////////////////////////////// -void dependencyChainOnStart (Task& task) -{ - if (Context::getContext ().config.getBoolean ("dependency.reminder")) - { - auto blocking = task.getDependencyTasks (); +void dependencyChainOnStart(Task& task) { + if (Context::getContext().config.getBoolean("dependency.reminder")) { + auto blocking = task.getDependencyTasks(); // If the task is anything but the tail end of a dependency chain, nag about // broken chain. - if (blocking.size ()) - { - std::cout << format (STRING_DEPEND_BLOCKED, task.identifier ()) - << '\n'; + if (blocking.size()) { + std::cout << format(STRING_DEPEND_BLOCKED, task.identifier()) << '\n'; for (const auto& b : blocking) - std::cout << " " << b.id << ' ' << b.get ("description") << '\n'; + std::cout << " " << b.id << ' ' << b.get("description") << '\n'; } } } diff --git a/src/dependency.h b/src/dependency.h new file mode 100644 index 000000000..66cfbd7a2 --- /dev/null +++ b/src/dependency.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_DEPENDENCY +#define INCLUDED_DEPENDENCY + +#include +// cmake.h include header must come first + +#include + +#define STRING_DEPEND_BLOCKED "Task {1} is blocked by:" +bool dependencyIsCircular(const Task& task); +void dependencyChainOnComplete(Task& task); +void dependencyChainOnStart(Task& task); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/feedback.cpp b/src/feedback.cpp index 131be6627..efd2302b4 100644 --- a/src/feedback.cpp +++ b/src/feedback.cpp @@ -25,38 +25,39 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include #include #include #include -#include -#include +#include #include +#include +#include -static void countTasks (const std::vector &, const std::string&, int&, int&); +#include +#include +#include +#include + +static void countTasks(const std::vector&, const std::string&, int&, int&); //////////////////////////////////////////////////////////////////////////////// -std::string renderAttribute (const std::string& name, const std::string& value, const std::string& format /* = "" */) -{ - if (Context::getContext ().columns.find (name) != Context::getContext ().columns.end ()) - { - Column* col = Context::getContext ().columns[name]; - if (col && - col->type () == "date" && - value != "") - { - Datetime d ((time_t)strtoll (value.c_str (), nullptr, 10)); - if (format == "") - return d.toString (Context::getContext ().config.get ("dateformat")); +std::string renderAttribute(const std::string& name, const std::string& value, + const std::string& format /* = "" */) { + if (Context::getContext().columns.find(name) != Context::getContext().columns.end()) { + Column* col = Context::getContext().columns[name]; + if (col && col->type() == "date" && value != "") { + int64_t epoch = strtoll(value.c_str(), nullptr, 10); + // Do not try to render an un-parseable value. + if (epoch == 0) { + return value; + } + Datetime d((time_t)epoch); + if (format == "") return d.toString(Context::getContext().config.get("dateformat")); - return d.toString (format); + return d.toString(format); } } @@ -66,10 +67,8 @@ std::string renderAttribute (const std::string& name, const std::string& value, //////////////////////////////////////////////////////////////////////////////// // Implements: // -void feedback_affected (const std::string& effect) -{ - if (Context::getContext ().verbose ("affected")) - std::cout << effect << "\n"; +void feedback_affected(const std::string& effect) { + if (Context::getContext().verbose("affected")) std::cout << effect << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -78,11 +77,8 @@ void feedback_affected (const std::string& effect) // // The 'effect' string should contain: // {1} Quantity -void feedback_affected (const std::string& effect, int quantity) -{ - if (Context::getContext ().verbose ("affected")) - std::cout << format (effect, quantity) - << "\n"; +void feedback_affected(const std::string& effect, int quantity) { + if (Context::getContext().verbose("affected")) std::cout << format(effect, quantity) << "\n"; } //////////////////////////////////////////////////////////////////////////////// @@ -92,76 +88,49 @@ void feedback_affected (const std::string& effect, int quantity) // The 'effect' string should contain: // {1} ID // {2} Description -void feedback_affected (const std::string& effect, const Task& task) -{ - if (Context::getContext ().verbose ("affected")) - { - std::cout << format (effect, - task.identifier (true), - task.get ("description")) - << "\n"; +void feedback_affected(const std::string& effect, const Task& task) { + if (Context::getContext().verbose("affected")) { + std::cout << format(effect, task.identifier(true), task.get("description")) << "\n"; } } //////////////////////////////////////////////////////////////////////////////// // Implements feedback and error when adding a reserved tag name. -void feedback_reserved_tags (const std::string& tag) -{ +void feedback_reserved_tags(const std::string& tag) { // Note: This list must match that in Task::hasTag. // Note: This list must match that in CmdInfo::execute. - if (tag == "ACTIVE" || - tag == "ANNOTATED" || - tag == "BLOCKED" || - tag == "BLOCKING" || - tag == "CHILD" || // Deprecated 2.6.0 - tag == "COMPLETED" || - tag == "DELETED" || - tag == "DUE" || - tag == "DUETODAY" || - tag == "INSTANCE" || - tag == "LATEST" || - tag == "MONTH" || - tag == "ORPHAN" || - tag == "OVERDUE" || - tag == "PARENT" || // Deprecated 2.6.0 - tag == "PENDING" || - tag == "PRIORITY" || - tag == "PROJECT" || - tag == "QUARTER" || - tag == "READY" || - tag == "SCHEDULED" || - tag == "TAGGED" || - tag == "TEMPLATE" || - tag == "TODAY" || - tag == "TOMORROW" || - tag == "UDA" || - tag == "UNBLOCKED" || - tag == "UNTIL" || - tag == "WAITING" || - tag == "WEEK" || - tag == "YEAR" || - tag == "YESTERDAY") - { - throw format ("Virtual tags (including '{1}') are reserved and may not be added or removed.", tag); + if (tag == "ACTIVE" || tag == "ANNOTATED" || tag == "BLOCKED" || tag == "BLOCKING" || + tag == "CHILD" || // Deprecated 2.6.0 + tag == "COMPLETED" || tag == "DELETED" || tag == "DUE" || tag == "DUETODAY" || + tag == "INSTANCE" || tag == "LATEST" || tag == "MONTH" || tag == "ORPHAN" || + tag == "OVERDUE" || tag == "PARENT" || // Deprecated 2.6.0 + tag == "PENDING" || tag == "PRIORITY" || tag == "PROJECT" || tag == "QUARTER" || + tag == "READY" || tag == "SCHEDULED" || tag == "TAGGED" || tag == "TEMPLATE" || + tag == "TODAY" || tag == "TOMORROW" || tag == "UDA" || tag == "UNBLOCKED" || tag == "UNTIL" || + tag == "WAITING" || tag == "WEEK" || tag == "YEAR" || tag == "YESTERDAY") { + throw format("Virtual tags (including '{1}') are reserved and may not be added or removed.", + tag); } } //////////////////////////////////////////////////////////////////////////////// // Implements feedback when adding special tags to a task. -void feedback_special_tags (const Task& task, const std::string& tag) -{ - if (Context::getContext ().verbose ("special")) - { +void feedback_special_tags(const Task& task, const std::string& tag) { + if (Context::getContext().verbose("special")) { std::string msg; - if (tag == "nocolor") msg = "The 'nocolor' special tag will disable color rules for this task."; - else if (tag == "nonag") msg = "The 'nonag' special tag will prevent nagging when this task is modified."; - else if (tag == "nocal") msg = "The 'nocal' special tag will keep this task off the 'calendar' report."; - else if (tag == "next") msg = "The 'next' special tag will boost the urgency of this task so it appears on the 'next' report."; + if (tag == "nocolor") + msg = "The 'nocolor' special tag will disable color rules for this task."; + else if (tag == "nonag") + msg = "The 'nonag' special tag will prevent nagging when this task is modified."; + else if (tag == "nocal") + msg = "The 'nocal' special tag will keep this task off the 'calendar' report."; + else if (tag == "next") + msg = + "The 'next' special tag will boost the urgency of this task so it appears on the 'next' " + "report."; - if (msg.length ()) - { - std::cout << format (msg, task.identifier ()) - << "\n"; + if (msg.length()) { + std::cout << format(msg, task.identifier()) << "\n"; } } } @@ -173,31 +142,20 @@ void feedback_special_tags (const Task& task, const std::string& tag) // // Implements: // Unblocked '' -void feedback_unblocked (const Task& task) -{ - if (Context::getContext ().verbose ("affected")) - { +void feedback_unblocked(const Task& task) { + if (Context::getContext().verbose("affected")) { // Get a list of tasks that depended on this task. - auto blocked = task.getBlockedTasks (); + auto blocked = task.getBlockedTasks(); // Scan all the tasks that were blocked by this task - for (auto& i : blocked) - { - auto blocking = i.getDependencyTasks (); - if (blocking.size () == 0) - { + for (auto& i : blocked) { + auto blocking = i.getDependencyTasks(); + if (blocking.size() == 0) { if (i.id) - std::cout << format ("Unblocked {1} '{2}'.", - i.id, - i.get ("description")) - << "\n"; - else - { - std::string uuid = i.get ("uuid"); - std::cout << format ("Unblocked {1} '{2}'.", - i.get ("uuid"), - i.get ("description")) - << "\n"; + std::cout << format("Unblocked {1} '{2}'.", i.id, i.get("description")) << "\n"; + else { + std::string uuid = i.get("uuid"); + std::cout << format("Unblocked {1} '{2}'.", i.get("uuid"), i.get("description")) << "\n"; } } } @@ -205,39 +163,35 @@ void feedback_unblocked (const Task& task) } /////////////////////////////////////////////////////////////////////////////// -void feedback_backlog () -{ +void feedback_backlog() { // If non-local sync is not set up, do not provide this feedback. - if (Context::getContext ().config.get ("sync.encryption_secret") == "") { + if (Context::getContext().config.get("sync.encryption_secret") == "") { return; } - if (Context::getContext ().verbose ("sync")) - { - int count = Context::getContext ().tdb2.num_local_changes (); + if (Context::getContext().verbose("sync")) { + int count = Context::getContext().tdb2.num_local_changes(); if (count) - Context::getContext ().footnote (format (count > 1 ? "There are {1} local changes. Sync required." - : "There is {1} local change. Sync required.", count)); + Context::getContext().footnote(format(count > 1 + ? "There are {1} local changes. Sync required." + : "There is {1} local change. Sync required.", + count)); } } /////////////////////////////////////////////////////////////////////////////// -std::string onProjectChange (Task& task, bool scope /* = true */) -{ +std::string onProjectChange(Task& task, bool scope /* = true */) { std::stringstream msg; - std::string project = task.get ("project"); + std::string project = task.get("project"); - if (project != "") - { - if (scope) - msg << format ("The project '{1}' has changed.", project) - << " "; + if (project != "") { + if (scope) msg << format("The project '{1}' has changed.", project) << " "; // Count pending and done tasks, for this project. int count_pending = 0; int count_done = 0; - std::vector all = Context::getContext ().tdb2.all_tasks (); - countTasks (all, project, count_pending, count_done); + std::vector all = Context::getContext().tdb2.all_tasks(); + countTasks(all, project, count_pending, count_done); // count_done count_pending percentage // ---------- ------------- ---------- @@ -253,72 +207,59 @@ std::string onProjectChange (Task& task, bool scope /* = true */) else percentage = (count_done * 100 / (count_done + count_pending)); - msg << format ("Project '{1}' is {2}% complete", project, percentage) - << ' '; + msg << format("Project '{1}' is {2}% complete", project, percentage) << ' '; if (count_pending == 1 && count_done == 0) - msg << format ("({1} task remaining).", count_pending); + msg << format("({1} task remaining).", count_pending); else - msg << format ("({1} of {2} tasks remaining).", count_pending, count_pending + count_done); + msg << format("({1} of {2} tasks remaining).", count_pending, count_pending + count_done); } - return msg.str (); + return msg.str(); } /////////////////////////////////////////////////////////////////////////////// -std::string onProjectChange (Task& task1, Task& task2) -{ - if (task1.get ("project") == task2.get ("project")) - return onProjectChange (task1, false); +std::string onProjectChange(Task& task1, Task& task2) { + if (task1.get("project") == task2.get("project")) return onProjectChange(task1, false); - std::string messages1 = onProjectChange (task1); - std::string messages2 = onProjectChange (task2); + std::string messages1 = onProjectChange(task1); + std::string messages2 = onProjectChange(task2); - if (messages1.length () && messages2.length ()) - return messages1 + '\n' + messages2; + if (messages1.length() && messages2.length()) return messages1 + '\n' + messages2; return messages1 + messages2; } /////////////////////////////////////////////////////////////////////////////// -std::string onExpiration (Task& task) -{ +std::string onExpiration(Task& task) { std::stringstream msg; - if (Context::getContext ().verbose ("affected")) - msg << format ("Task {1} '{2}' expired and was deleted.", - task.identifier (true), - task.get ("description")); + if (Context::getContext().verbose("affected")) + msg << format("Task {1} '{2}' expired and was deleted.", task.identifier(true), + task.get("description")); - return msg.str (); + return msg.str(); } /////////////////////////////////////////////////////////////////////////////// -static void countTasks ( - const std::vector & all, - const std::string& project, - int& count_pending, - int& count_done) -{ - for (auto& it : all) - { - if (it.get ("project") == project) - { - switch (it.getStatus ()) - { - case Task::pending: - case Task::waiting: - ++count_pending; - break; +static void countTasks(const std::vector& all, const std::string& project, int& count_pending, + int& count_done) { + for (auto& it : all) { + if (it.get("project") == project) { + switch (it.getStatus()) { + case Task::pending: + case Task::waiting: + ++count_pending; + break; - case Task::completed: - ++count_done; - break; + case Task::completed: + ++count_done; + break; - case Task::deleted: - case Task::recurring: - default: - break; + case Task::deleted: + case Task::recurring: + default: + break; } } } diff --git a/src/feedback.h b/src/feedback.h new file mode 100644 index 000000000..55b565c00 --- /dev/null +++ b/src/feedback.h @@ -0,0 +1,54 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_FEEDBACK +#define INCLUDED_FEEDBACK + +#include +// cmake.h include header must come first + +#include + +#include +#include + +std::string renderAttribute(const std::string& name, const std::string& value, + const std::string& format = ""); +void feedback_affected(const std::string& effect); +void feedback_affected(const std::string& effect, int quantity); +void feedback_affected(const std::string& effect, const Task& task); +void feedback_reserved_tags(const std::string& tag); +void feedback_special_tags(const Task& task, const std::string& tag); +void feedback_unblocked(const Task& task); +void feedback_backlog(); +std::string onProjectChange(Task& task, bool scope = true); +std::string onProjectChange(Task& task1, Task& task2); +std::string onExpiration(Task& task); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/legacy.cpp b/src/legacy.cpp index 7cf9f9321..a14be7f2f 100644 --- a/src/legacy.cpp +++ b/src/legacy.cpp @@ -25,16 +25,17 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include +// cmake.h include header must come first + #include #include +#include + #define STRING_LEGACY_PRIORITY "Legacy attribute found. Please change '{1}' to '{2}'." //////////////////////////////////////////////////////////////////////////////// -void legacyColumnMap (std::string& name) -{ +void legacyColumnMap(std::string& name) { // 2014-01-26: priority_long --> priority.long Mapping removed // 2014-01-26: entry_time --> entry Mapping removed // 2014-01-26: start_time --> start Mapping removed @@ -49,20 +50,18 @@ void legacyColumnMap (std::string& name) // 2014-01-26: description_only --> description.desc Mapping removed // One-time initialization, on demand. - static std::map legacyMap {{"priority.", "priority"}}; + static std::map legacyMap{{"priority.", "priority"}}; // If a legacy column was used, complain about it, but modify it anyway. - auto found = legacyMap.find (name); - if (found != legacyMap.end ()) - { - Context::getContext ().footnote (format (STRING_LEGACY_PRIORITY, name, found->second)); + auto found = legacyMap.find(name); + if (found != legacyMap.end()) { + Context::getContext().footnote(format(STRING_LEGACY_PRIORITY, name, found->second)); name = found->second; } } //////////////////////////////////////////////////////////////////////////////// -void legacySortColumnMap (std::string& name) -{ +void legacySortColumnMap(std::string& name) { // 2014-01-26: priority_long --> priority Mapping removed // 2014-01-26: entry_time --> entry Mapping removed // 2014-01-26: start_time --> start Mapping removed @@ -77,97 +76,83 @@ void legacySortColumnMap (std::string& name) // 2014-01-26: description_only --> description Mapping removed // One-time initialization, on demand. - static std::map legacyMap {{"priority.", "priority"}}; + static std::map legacyMap{{"priority.", "priority"}}; // If a legacy column was used, complain about it, but modify it anyway. - auto found = legacyMap.find (name); - if (found != legacyMap.end ()) - { - Context::getContext ().footnote (format (STRING_LEGACY_PRIORITY, name, found->second)); + auto found = legacyMap.find(name); + if (found != legacyMap.end()) { + Context::getContext().footnote(format(STRING_LEGACY_PRIORITY, name, found->second)); name = found->second; } } //////////////////////////////////////////////////////////////////////////////// -std::string legacyCheckForDeprecatedVariables () -{ - std::vector deprecated; - for (auto& it : Context::getContext ().config) - { +std::string legacyCheckForDeprecatedVariables() { + std::vector deprecated; + for (auto& it : Context::getContext().config) { // 2014-07-04: report.*.limit removed. // 2016-02-24: alias._query removed. // Deprecated in 2.5.0. // report.*.annotations - if (it.first.length () > 19 && - it.first.substr (0, 7) == "report." && - it.first.substr (it.first.length () - 12) == ".annotations") - deprecated.push_back (it.first); + if (it.first.length() > 19 && it.first.substr(0, 7) == "report." && + it.first.substr(it.first.length() - 12) == ".annotations") + deprecated.push_back(it.first); // Deprecated in 2.5.0. - if (it.first == "next" || - it.first == "annotations" || - it.first == "export.ical.class") - deprecated.push_back (it.first); + if (it.first == "next" || it.first == "annotations" || it.first == "export.ical.class") + deprecated.push_back(it.first); // Deprecated in 2.5.0. - if (it.first == "urgency.inherit.coefficient") - deprecated.push_back (it.first); + if (it.first == "urgency.inherit.coefficient") deprecated.push_back(it.first); } std::stringstream out; - if (deprecated.size ()) - { + if (deprecated.size()) { out << "Your .taskrc file contains variables that are deprecated:\n"; - for (const auto& dep : deprecated) - out << " " << dep << "\n"; + for (const auto& dep : deprecated) out << " " << dep << "\n"; out << "\n"; } - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// -std::string legacyCheckForDeprecatedColumns () -{ - std::vector deprecated; - for (auto& it : Context::getContext ().config) - { - if (it.first.find ("report") == 0) - { +std::string legacyCheckForDeprecatedColumns() { + std::vector deprecated; + for (auto& it : Context::getContext().config) { + if (it.first.find("report") == 0) { // Deprecated in 2.0.0 - std::string value = Context::getContext ().config.get (it.first); - if (value.find ("entry_time") != std::string::npos || - value.find ("start_time") != std::string::npos || - value.find ("end_time") != std::string::npos) - deprecated.push_back (it.first); + std::string value = Context::getContext().config.get(it.first); + if (value.find("entry_time") != std::string::npos || + value.find("start_time") != std::string::npos || + value.find("end_time") != std::string::npos) + deprecated.push_back(it.first); } } std::stringstream out; out << "\n"; - if (deprecated.size ()) - { - out << "Your .taskrc file contains reports with deprecated columns. Please check for entry_time, start_time or end_time in:\n"; + if (deprecated.size()) { + out << "Your .taskrc file contains reports with deprecated columns. Please check for " + "entry_time, start_time or end_time in:\n"; for (const auto& dep : deprecated) - out << " " << dep << "=" << Context::getContext ().config.get (dep) << "\n"; + out << " " << dep << "=" << Context::getContext().config.get(dep) << "\n"; out << "\n"; } - return out.str (); + return out.str(); } //////////////////////////////////////////////////////////////////////////////// -void legacyAttributeMap (std::string& name) -{ +void legacyAttributeMap(std::string& name) { // TW-1274, 2.4.0 - if (name == "modification") - name = "modified"; + if (name == "modification") name = "modified"; } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/legacy.h b/src/legacy.h new file mode 100644 index 000000000..9e3258079 --- /dev/null +++ b/src/legacy.h @@ -0,0 +1,44 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_LEGACY +#define INCLUDED_LEGACY + +#include +// cmake.h include header must come first + +#include + +void legacyColumnMap(std::string& name); +void legacySortColumnMap(std::string& name); +std::string legacyCheckForDeprecatedVariables(); +std::string legacyCheckForDeprecatedColumns(); +void legacyAttributeMap(std::string& name); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/lex.cpp b/src/lex.cpp index 366962bc7..6e675eef5 100644 --- a/src/lex.cpp +++ b/src/lex.cpp @@ -1,22 +1,21 @@ //////////////////////////////////////////////////////////////////////////////// -#include -#include #include +#include + +#include Context context; -int main (int argc, char** argv) -{ - for (auto i = 1; i < argc; i++) - { +int main(int argc, char** argv) { + for (auto i = 1; i < argc; i++) { std::cout << "argument '" << argv[i] << "'\n"; - Lexer l (argv[i]); + Lexer l(argv[i]); std::string token; Lexer::Type type; - while (l.token (token, type)) - std::cout << " token '" << token << "' " << Lexer::typeToString (type) << "\n"; + while (l.token(token, type)) + std::cout << " token '" << token << "' " << Lexer::typeToString(type) << "\n"; } } diff --git a/src/libshared b/src/libshared index 47c3262fa..121f757c3 160000 --- a/src/libshared +++ b/src/libshared @@ -1 +1 @@ -Subproject commit 47c3262fa97c4b69542040d39be6c516c38d0e57 +Subproject commit 121f757c3ec1b1f548f7835208b8c72d85d141a7 diff --git a/src/main.cpp b/src/main.cpp index 07ac2fd22..b202cd6ad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -25,53 +25,57 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + +#include +#include +#include + +#include #include #include -#include -#include #include //////////////////////////////////////////////////////////////////////////////// -int main (int argc, const char** argv) -{ - int status {0}; +int main(int argc, const char** argv) { + int status{0}; + + // Ignore SIGPIPE from writes to network sockets after the remote end has hung + // up. Rust code expects this, and the Rust runtime ignores this signal at startup. + signal(SIGPIPE, SIG_IGN); Context globalContext; - Context::setContext (&globalContext); + Context::setContext(&globalContext); // Lightweight version checking that doesn't require initialization or any I/O. - if (argc == 2 && !strcmp (argv[1], "--version")) - { + if (argc == 2 && !strcmp(argv[1], "--version")) { std::cout << VERSION << "\n"; - } - else - { - try - { - status = Context::getContext ().initialize (argc, argv); - if (status == 0) - status = Context::getContext ().run (); + } else { + try { + status = Context::getContext().initialize(argc, argv); + if (status == 0) status = Context::getContext().run(); } - catch (const std::string& error) - { + catch (const std::string& error) { std::cerr << error << "\n"; status = -1; } - catch (std::bad_alloc& error) - { - std::cerr << "Error: Memory allocation failed: " << error.what () << "\n"; + catch (rust::Error& err) { + std::cerr << err.what() << "\n"; + status = -1; + } + + catch (std::bad_alloc& error) { + std::cerr << "Error: Memory allocation failed: " << error.what() << "\n"; status = -3; } - catch (const std::regex_error& e) - { + catch (const std::regex_error& e) { std::cout << "regex_error caught: " << e.what() << '\n'; } - catch (...) - { + catch (...) { std::cerr << "Unknown error. Please report.\n"; status = -2; } diff --git a/src/main.h b/src/main.h deleted file mode 100644 index 3b29a74f1..000000000 --- a/src/main.h +++ /dev/null @@ -1,92 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_MAIN -#define INCLUDED_MAIN - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// recur.cpp -void handleRecurrence (); -void handleUntil (); -Datetime getNextRecurrence (Datetime&, std::string&); -bool generateDueDates (Task&, std::vector &); -void updateRecurrenceMask (Task&); - -// recur2.cpp -void handleRecurrence2 (); - -// nag.cpp -void nag (std::vector &); - -// rules.cpp -void initializeColorRules (); -void autoColorize (Task&, Color&); -std::string colorizeHeader (const std::string&); -std::string colorizeFootnote (const std::string&); -std::string colorizeError (const std::string&); -std::string colorizeDebug (const std::string&); - -// dependency.cpp -bool dependencyIsCircular (const Task&); -void dependencyChainOnComplete (Task&); -void dependencyChainOnStart (Task&); - -// feedback.cpp -std::string renderAttribute (const std::string&, const std::string&, const std::string& format = ""); -void feedback_affected (const std::string&); -void feedback_affected (const std::string&, int); -void feedback_affected (const std::string&, const Task&); -void feedback_reserved_tags (const std::string&); -void feedback_special_tags (const Task&, const std::string&); -void feedback_unblocked (const Task&); -void feedback_backlog (); -std::string onProjectChange (Task&, bool scope = true); -std::string onProjectChange (Task&, Task&); -std::string onExpiration (Task&); - -// sort.cpp -void sort_tasks (std::vector &, std::vector &, const std::string&); -void sort_projects (std::list >& sorted, std::map & allProjects); -void sort_projects (std::list >& sorted, std::map & allProjects); - -// legacy.cpp -void legacyColumnMap (std::string&); -void legacySortColumnMap (std::string&); -std::string legacyCheckForDeprecatedVariables (); -std::string legacyCheckForDeprecatedColumns (); -void legacyAttributeMap (std::string&); - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/nag.cpp b/src/nag.cpp index f95c5201b..356287f45 100644 --- a/src/nag.cpp +++ b/src/nag.cpp @@ -24,33 +24,25 @@ // //////////////////////////////////////////////////////////////////////////////// -#include #include +// cmake.h include header must come first #include -#include -#include -#include + #include //////////////////////////////////////////////////////////////////////////////// // Generates a nag message when there are READY tasks of a higher urgency. -void nag (std::vector & tasks) -{ - auto msg = Context::getContext ().config.get ("nag"); - if (msg == "") - return; +void nag(std::vector& tasks) { + auto msg = Context::getContext().config.get("nag"); + if (msg == "") return; - auto pending = Context::getContext ().tdb2.pending_tasks (); + auto pending = Context::getContext().tdb2.pending_tasks(); for (auto& t1 : tasks) { - if (t1.hasTag ("nonag")) - continue; + if (t1.hasTag("nonag")) continue; for (auto& t2 : pending) { - if (t1.get ("uuid") != t2.get ("uuid") && - t2.hasTag ("READY") && - t1.urgency () < t2.urgency ()) - { - Context::getContext ().footnote (msg); + if (t1.get("uuid") != t2.get("uuid") && t2.hasTag("READY") && t1.urgency() < t2.urgency()) { + Context::getContext().footnote(msg); return; } } diff --git a/src/nag.h b/src/nag.h new file mode 100644 index 000000000..6a8d2a1b5 --- /dev/null +++ b/src/nag.h @@ -0,0 +1,43 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_NAG +#define INCLUDED_NAG + +#include +// cmake.h include header must come first +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// Generates a nag message when there are READY tasks of a higher urgency. +void nag(std::vector& tasks); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/recur.cpp b/src/recur.cpp index 524c912d3..482fc1060 100644 --- a/src/recur.cpp +++ b/src/recur.cpp @@ -25,109 +25,117 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include -#include #include #include +#include +#include #include +#include +#include +#include +#include +#include #include +#include #include -#include + +#include +#include + +// Add a `time_t` delta to a Datetime, checking for and returning nullopt on integer overflow. +std::optional checked_add_datetime(Datetime& base, time_t delta) { + // Datetime::operator+ takes an integer delta, so check that range + if (static_cast(std::numeric_limits::max()) < delta) { + return std::nullopt; + } + + // Check for time_t overflow in the Datetime. + if (std::numeric_limits::max() - base.toEpoch() < delta) { + return std::nullopt; + } + return base + delta; +} //////////////////////////////////////////////////////////////////////////////// // Scans all tasks, and for any recurring tasks, determines whether any new // child tasks need to be generated to fill gaps. -void handleRecurrence () -{ +void handleRecurrence() { // Recurrence can be disabled. // Note: This is currently a workaround for TD-44, TW-1520. - if (! Context::getContext ().config.getBoolean ("recurrence")) - return; + if (!Context::getContext().config.getBoolean("recurrence")) return; - auto tasks = Context::getContext ().tdb2.pending_tasks (); + auto tasks = Context::getContext().tdb2.pending_tasks(); Datetime now; // Look at all tasks and find any recurring ones. - for (auto& t : tasks) - { - if (t.getStatus () == Task::recurring) - { + for (auto& t : tasks) { + if (t.getStatus() == Task::recurring) { // Generate a list of due dates for this recurring task, regardless of // the mask. - std::vector due; - if (! generateDueDates (t, due)) - { + std::vector due; + if (!generateDueDates(t, due)) { // Determine the end date. - t.setStatus (Task::deleted); - Context::getContext ().tdb2.modify (t); - Context::getContext ().footnote (onExpiration (t)); + t.setStatus(Task::deleted); + Context::getContext().tdb2.modify(t); + Context::getContext().footnote(onExpiration(t)); continue; } // Get the mask from the parent task. - auto mask = t.get ("mask"); + auto mask = t.get("mask"); // Iterate over the due dates, and check each against the mask. auto changed = false; unsigned int i = 0; - for (auto& d : due) - { - if (mask.length () <= i) - { + for (auto& d : due) { + if (mask.length() <= i) { changed = true; - Task rec (t); // Clone the parent. - rec.setStatus (Task::pending); // Change the status. - rec.set ("uuid", uuid ()); // New UUID. - rec.set ("parent", t.get ("uuid")); // Remember mom. - rec.setAsNow ("entry"); // New entry date. - rec.set ("due", format (d.toEpoch ())); + Task rec(t); // Clone the parent. + rec.setStatus(Task::pending); // Change the status. + rec.set("uuid", uuid()); // New UUID. + rec.set("parent", t.get("uuid")); // Remember mom. + rec.setAsNow("entry"); // New entry date. + rec.set("due", format(d.toEpoch())); - if (t.has ("wait")) - { - Datetime old_wait (t.get_date ("wait")); - Datetime old_due (t.get_date ("due")); - Datetime due (d); - rec.set ("wait", format ((due + (old_wait - old_due)).toEpoch ())); - rec.setStatus (Task::waiting); + if (t.has("wait")) { + Datetime old_wait(t.get_date("wait")); + Datetime old_due(t.get_date("due")); + Datetime due(d); + auto wait = checked_add_datetime(due, old_wait - old_due); + if (wait) { + rec.set("wait", format(wait->toEpoch())); + } else { + rec.remove("wait"); + } + rec.setStatus(Task::waiting); mask += 'W'; - } - else - { + } else { mask += '-'; - rec.setStatus (Task::pending); + rec.setStatus(Task::pending); } - rec.set ("imask", i); - rec.remove ("mask"); // Remove the mask of the parent. + rec.set("imask", i); + rec.remove("mask"); // Remove the mask of the parent. // Add the new task to the DB. - Context::getContext ().tdb2.add (rec); + Context::getContext().tdb2.add(rec); } ++i; } // Only modify the parent if necessary. - if (changed) - { - t.set ("mask", mask); - Context::getContext ().tdb2.modify (t); + if (changed) { + t.set("mask", mask); + Context::getContext().tdb2.modify(t); - if (Context::getContext ().verbose ("recur")) - Context::getContext ().footnote (format ("Creating recurring task instance '{1}'", t.get ("description"))); + if (Context::getContext().verbose("recur")) + Context::getContext().footnote( + format("Creating recurring task instance '{1}'", t.get("description"))); } } } @@ -138,292 +146,258 @@ void handleRecurrence () // period (recur). Then generate a set of corresponding dates. // // Returns false if the parent recurring task is depleted. -bool generateDueDates (Task& parent, std::vector & allDue) -{ +bool generateDueDates(Task& parent, std::vector& allDue) { // Determine due date, recur period and until date. - Datetime due (parent.get_date ("due")); - if (due._date == 0) - return false; + Datetime due(parent.get_date("due")); + if (due._date == 0) return false; - std::string recur = parent.get ("recur"); + std::string recur = parent.get("recur"); bool specificEnd = false; Datetime until; - if (parent.get ("until") != "") - { - until = Datetime (parent.get ("until")); + if (parent.get("until") != "") { + until = Datetime(parent.get("until")); specificEnd = true; } - auto recurrence_limit = Context::getContext ().config.getInteger ("recurrence.limit"); + auto recurrence_limit = Context::getContext().config.getInteger("recurrence.limit"); int recurrence_counter = 0; Datetime now; - for (Datetime i = due; ; i = getNextRecurrence (i, recur)) - { - allDue.push_back (i); + Datetime i = due; + while (1) { + allDue.push_back(i); - if (specificEnd && i > until) - { + if (specificEnd && i > until) { // If i > until, it means there are no more tasks to generate, and if the // parent mask contains all + or X, then there never will be another task // to generate, and this parent task may be safely reaped. - auto mask = parent.get ("mask"); - if (mask.length () == allDue.size () && - mask.find ('-') == std::string::npos) - return false; + auto mask = parent.get("mask"); + if (mask.length() == allDue.size() && mask.find('-') == std::string::npos) return false; return true; } - if (i > now) - ++recurrence_counter; + if (i > now) ++recurrence_counter; - if (recurrence_counter >= recurrence_limit) + if (recurrence_counter >= recurrence_limit) return true; + auto next = getNextRecurrence(i, recur); + if (next) { + i = *next; + } else { return true; + } } return true; } //////////////////////////////////////////////////////////////////////////////// -Datetime getNextRecurrence (Datetime& current, std::string& period) -{ - auto m = current.month (); - auto d = current.day (); - auto y = current.year (); - auto ho = current.hour (); - auto mi = current.minute (); - auto se = current.second (); +/// Determine the next recurrence of the given period. +/// +/// If no such date can be calculated, such as with a very large period, returns +/// nullopt. +std::optional getNextRecurrence(Datetime& current, std::string& period) { + auto m = current.month(); + auto d = current.day(); + auto y = current.year(); + auto ho = current.hour(); + auto mi = current.minute(); + auto se = current.second(); // Some periods are difficult, because they can be vague. - if (period == "monthly" || - period == "P1M") - { - if (++m > 12) - { - m -= 12; - ++y; + if (period == "monthly" || period == "P1M") { + if (++m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period == "weekdays") - { - auto dow = current.dayOfWeek (); + else if (period == "weekdays") { + auto dow = current.dayOfWeek(); int days; - if (dow == 5) days = 3; - else if (dow == 6) days = 2; - else days = 1; + if (dow == 5) + days = 3; + else if (dow == 6) + days = 2; + else + days = 1; - return current + (days * 86400); + return checked_add_datetime(current, days * 86400); } - else if (unicodeLatinDigit (period[0]) && - period[period.length () - 1] == 'm') - { - int increment = strtol (period.substr (0, period.length () - 1).c_str (), nullptr, 10); + else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'm') { + int increment = strtol(period.substr(0, period.length() - 1).c_str(), nullptr, 10); if (increment <= 0) - throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment); + throw format("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, + increment); m += increment; - while (m > 12) - { - m -= 12; - ++y; + while (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period[0] == 'P' && - Lexer::isAllDigits (period.substr (1, period.length () - 2)) && - period[period.length () - 1] == 'M') - { - int increment = strtol (period.substr (1, period.length () - 2).c_str (), nullptr, 10); + else if (period[0] == 'P' && Lexer::isAllDigits(period.substr(1, period.length() - 2)) && + period[period.length() - 1] == 'M') { + int increment = strtol(period.substr(1, period.length() - 2).c_str(), nullptr, 10); if (increment <= 0) - throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment); + throw format("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, + increment); m += increment; - while (m > 12) - { - m -= 12; - ++y; + while (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d); + return Datetime(y, m, d); } - else if (period == "quarterly" || - period == "P3M") - { + else if (period == "quarterly" || period == "P3M") { m += 3; - if (m > 12) - { - m -= 12; - ++y; + if (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (unicodeLatinDigit (period[0]) && period[period.length () - 1] == 'q') - { - int increment = strtol (period.substr (0, period.length () - 1).c_str (), nullptr, 10); + else if (unicodeLatinDigit(period[0]) && period[period.length() - 1] == 'q') { + int increment = strtol(period.substr(0, period.length() - 1).c_str(), nullptr, 10); - if (increment <= 0) - throw format ("Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment); + if (increment <= 0) { + Context::getContext().footnote(format( + "Recurrence period '{1}' is equivalent to {2} and hence invalid.", period, increment)); + return std::nullopt; + } m += 3 * increment; - while (m > 12) - { - m -= 12; - ++y; + while (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period == "semiannual" || - period == "P6M") - { + else if (period == "semiannual" || period == "P6M") { m += 6; - if (m > 12) - { - m -= 12; - ++y; + if (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period == "bimonthly" || - period == "P2M") - { + else if (period == "bimonthly" || period == "P2M") { m += 2; - if (m > 12) - { - m -= 12; - ++y; + if (m > 12) { + m -= 12; + ++y; } - while (! Datetime::valid (y, m, d)) - --d; + while (!Datetime::valid(y, m, d)) --d; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period == "biannual" || - period == "biyearly" || - period == "P2Y") - { + else if (period == "biannual" || period == "biyearly" || period == "P2Y") { y += 2; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } - else if (period == "annual" || - period == "yearly" || - period == "P1Y") - { + else if (period == "annual" || period == "yearly" || period == "P1Y") { y += 1; // If the due data just happens to be 2/29 in a leap year, then simply // incrementing y is going to create an invalid date. - if (m == 2 && d == 29) - d = 28; + if (m == 2 && d == 29) d = 28; - return Datetime (y, m, d, ho, mi, se); + return Datetime(y, m, d, ho, mi, se); } // Add the period to current, and we're done. std::string::size_type idx = 0; Duration p; - if (! p.parse (period, idx)) - throw std::string (format ("The recurrence value '{1}' is not valid.", period)); + if (!p.parse(period, idx)) { + Context::getContext().footnote( + format("Warning: The recurrence value '{1}' is not valid.", period)); + return std::nullopt; + } - return current + p.toTime_t (); + return checked_add_datetime(current, p.toTime_t()); } //////////////////////////////////////////////////////////////////////////////// // When the status of a recurring child task changes, the parent task must // update it's mask. -void updateRecurrenceMask (Task& task) -{ - auto uuid = task.get ("parent"); +void updateRecurrenceMask(Task& task) { + auto uuid = task.get("parent"); Task parent; - if (uuid != "" && - Context::getContext ().tdb2.get (uuid, parent)) - { - unsigned int index = strtol (task.get ("imask").c_str (), nullptr, 10); - auto mask = parent.get ("mask"); - if (mask.length () > index) - { - mask[index] = (task.getStatus () == Task::pending) ? '-' - : (task.getStatus () == Task::completed) ? '+' - : (task.getStatus () == Task::deleted) ? 'X' - : (task.getStatus () == Task::waiting) ? 'W' - : '?'; - } - else - { + if (uuid != "" && Context::getContext().tdb2.get(uuid, parent)) { + unsigned int index = strtol(task.get("imask").c_str(), nullptr, 10); + auto mask = parent.get("mask"); + if (mask.length() > index) { + mask[index] = (task.getStatus() == Task::pending) ? '-' + : (task.getStatus() == Task::completed) ? '+' + : (task.getStatus() == Task::deleted) ? 'X' + : (task.getStatus() == Task::waiting) ? 'W' + : '?'; + } else { std::string mask; - for (unsigned int i = 0; i < index; ++i) - mask += "?"; + for (unsigned int i = 0; i < index; ++i) mask += "?"; - mask += (task.getStatus () == Task::pending) ? '-' - : (task.getStatus () == Task::completed) ? '+' - : (task.getStatus () == Task::deleted) ? 'X' - : (task.getStatus () == Task::waiting) ? 'W' - : '?'; + mask += (task.getStatus() == Task::pending) ? '-' + : (task.getStatus() == Task::completed) ? '+' + : (task.getStatus() == Task::deleted) ? 'X' + : (task.getStatus() == Task::waiting) ? 'W' + : '?'; } - parent.set ("mask", mask); - Context::getContext ().tdb2.modify (parent); + parent.set("mask", mask); + Context::getContext().tdb2.modify(parent); } } //////////////////////////////////////////////////////////////////////////////// // Delete expired tasks. -void handleUntil () -{ +void handleUntil() { Datetime now; - auto tasks = Context::getContext ().tdb2.pending_tasks (); - for (auto& t : tasks) - { + auto tasks = Context::getContext().tdb2.pending_tasks(); + for (auto& t : tasks) { // TODO What about expiring template tasks? - if (t.getStatus () == Task::pending && - t.has ("until")) - { - auto until = Datetime (t.get_date ("until")); - if (until < now) - { - Context::getContext ().debug (format ("handleUntil: recurrence expired until {1} < now {2}", until.toISOLocalExtended (), now.toISOLocalExtended ())); - t.setStatus (Task::deleted); - Context::getContext ().tdb2.modify(t); - Context::getContext ().footnote (onExpiration (t)); + if (t.getStatus() == Task::pending && t.has("until")) { + auto until = Datetime(t.get_date("until")); + if (until < now) { + Context::getContext().debug(format("handleUntil: recurrence expired until {1} < now {2}", + until.toISOLocalExtended(), now.toISOLocalExtended())); + t.setStatus(Task::deleted); + Context::getContext().tdb2.modify(t); + Context::getContext().footnote(onExpiration(t)); } } } diff --git a/src/recur.h b/src/recur.h new file mode 100644 index 000000000..db0b6306d --- /dev/null +++ b/src/recur.h @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_RECUR +#define INCLUDED_RECUR + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +std::optional checked_add_datetime(Datetime& base, time_t delta); +void handleRecurrence(); +bool generateDueDates(Task& parent, std::vector& allDue); +std::optional getNextRecurrence(Datetime& current, std::string& period); +void updateRecurrenceMask(Task& task); +void handleUntil(); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/rules.cpp b/src/rules.cpp index 760858487..21c46307d 100644 --- a/src/rules.cpp +++ b/src/rules.cpp @@ -25,175 +25,146 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include +// cmake.h include header must come first + #include #include +#include #include -#include -static std::map gsColor; -static std::vector gsPrecedence; +static std::map gsColor; +static std::vector gsPrecedence; static Datetime now; //////////////////////////////////////////////////////////////////////////////// -void initializeColorRules () -{ +void initializeColorRules() { // If color is not enable/supported, short circuit. - if (! Context::getContext ().color ()) - return; + if (!Context::getContext().color()) return; - try - { - gsColor.clear (); - gsPrecedence.clear (); + try { + gsColor.clear(); + gsPrecedence.clear(); // Load all the configuration values, filter to only the ones that begin with // "color.", then store name/value in gsColor, and name in rules. - std::vector rules; - for (const auto& v : Context::getContext ().config) - { - if (! v.first.compare (0, 6, "color.", 6)) - { - Color c (v.second); + std::vector rules; + for (const auto& v : Context::getContext().config) { + if (!v.first.compare(0, 6, "color.", 6)) { + Color c(v.second); gsColor[v.first] = c; - rules.push_back (v.first); + rules.push_back(v.first); } } // Load the rule.precedence.color list, split it, then autocomplete against // the 'rules' vector loaded above. - std::vector results; - auto precedence = split (Context::getContext ().config.get ("rule.precedence.color"), ','); + std::vector results; + auto precedence = split(Context::getContext().config.get("rule.precedence.color"), ','); - for (const auto& p : precedence) - { + for (const auto& p : precedence) { // Add the leading "color." string. std::string rule = "color." + p; - autoComplete (rule, rules, results, 3); // Hard-coded 3. + autoComplete(rule, rules, results, 3); // Hard-coded 3. - for (auto& r : results) - gsPrecedence.push_back (r); + for (auto& r : results) gsPrecedence.push_back(r); } } - catch (const std::string& e) - { - Context::getContext ().error (e); + catch (const std::string& e) { + Context::getContext().error(e); } } //////////////////////////////////////////////////////////////////////////////// -static void applyColor (const Color& base, Color& c, bool merge) -{ +static void applyColor(const Color& base, Color& c, bool merge) { if (merge) - c.blend (base); + c.blend(base); else c = base; } //////////////////////////////////////////////////////////////////////////////// -static void colorizeBlocked (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.is_blocked) - applyColor (base, c, merge); +static void colorizeBlocked(Task& task, const Color& base, Color& c, bool merge) { + if (task.is_blocked) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeBlocking (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.is_blocking) - applyColor (base, c, merge); +static void colorizeBlocking(Task& task, const Color& base, Color& c, bool merge) { + if (task.is_blocking) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeTagged (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.getTagCount ()) - applyColor (base, c, merge); +static void colorizeTagged(Task& task, const Color& base, Color& c, bool merge) { + if (task.getTagCount()) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeActive (Task& task, const Color& base, Color& c, bool merge) -{ +static void colorizeActive(Task& task, const Color& base, Color& c, bool merge) { // TODO: Not consistent with the implementation of the +ACTIVE virtual tag - if (task.has ("start") && - !task.has ("end")) - applyColor (base, c, merge); + if (task.has("start") && !task.has("end")) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeScheduled (Task& task, const Color& base, Color& c, bool merge) -{ +static void colorizeScheduled(Task& task, const Color& base, Color& c, bool merge) { // TODO: Not consistent with the implementation of the +SCHEDULED virtual tag - if (task.has ("scheduled") && - Datetime (task.get_date ("scheduled")) <= now) - applyColor (base, c, merge); + if (task.has("scheduled") && Datetime(task.get_date("scheduled")) <= now) + applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeUntil (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.has ("until")) - applyColor (base, c, merge); +static void colorizeUntil(Task& task, const Color& base, Color& c, bool merge) { + if (task.has("until")) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeTag (Task& task, const std::string& rule, const Color& base, Color& c, bool merge) -{ - if (task.hasTag (rule.substr (10))) - applyColor (base, c, merge); +static void colorizeTag(Task& task, const std::string& rule, const Color& base, Color& c, + bool merge) { + if (task.hasTag(rule.substr(10))) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeProject (Task& task, const std::string& rule, const Color& base, Color& c, bool merge) -{ +static void colorizeProject(Task& task, const std::string& rule, const Color& base, Color& c, + bool merge) { // Observe the case sensitivity setting. - bool sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive"); + bool sensitive = Context::getContext().config.getBoolean("search.case.sensitive"); - auto project = task.get ("project"); - auto rule_trunc = rule.substr (14); + auto project = task.get("project"); + auto rule_trunc = rule.substr(14); // Match project names leftmost. - if (rule_trunc.length () <= project.length ()) - if (compare (rule_trunc, project.substr (0, rule_trunc.length ()), sensitive)) - applyColor (base, c, merge); + if (rule_trunc.length() <= project.length()) + if (compare(rule_trunc, project.substr(0, rule_trunc.length()), sensitive)) + applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeProjectNone (Task& task, const Color& base, Color& c, bool merge) -{ - if(!task.has ("project")) - applyColor (base, c, merge); +static void colorizeProjectNone(Task& task, const Color& base, Color& c, bool merge) { + if (!task.has("project")) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeTagNone (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.getTagCount () == 0) - applyColor (base, c, merge); +static void colorizeTagNone(Task& task, const Color& base, Color& c, bool merge) { + if (task.getTagCount() == 0) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeKeyword (Task& task, const std::string& rule, const Color& base, Color& c, bool merge) -{ +static void colorizeKeyword(Task& task, const std::string& rule, const Color& base, Color& c, + bool merge) { // Observe the case sensitivity setting. - auto sensitive = Context::getContext ().config.getBoolean ("search.case.sensitive"); + auto sensitive = Context::getContext().config.getBoolean("search.case.sensitive"); // The easiest thing to check is the description, because it is just one // attribute. - if (find (task.get ("description"), rule.substr (14), sensitive) != std::string::npos) - applyColor (base, c, merge); + if (find(task.get("description"), rule.substr(14), sensitive) != std::string::npos) + applyColor(base, c, merge); // Failing the description check, look at all annotations, returning on the // first match. - else - { - for (const auto& att : task.getAnnotations ()) - { - if (find (att.second, rule.substr (14), sensitive) != std::string::npos) - { - applyColor (base, c, merge); + else { + for (const auto& att : task.getAnnotations()) { + if (find(att.second, rule.substr(14), sensitive) != std::string::npos) { + applyColor(base, c, merge); return; } } @@ -201,146 +172,132 @@ static void colorizeKeyword (Task& task, const std::string& rule, const Color& b } //////////////////////////////////////////////////////////////////////////////// -static void colorizeUDA (Task& task, const std::string& rule, const Color& base, Color& c, bool merge) -{ +static void colorizeUDA(Task& task, const std::string& rule, const Color& base, Color& c, + bool merge) { // Is the rule color.uda.name.value or color.uda.name? - auto pos = rule.find ('.', 10); - if (pos == std::string::npos) - { - if (task.has (rule.substr (10))) - applyColor (base, c, merge); - } - else - { - auto uda = rule.substr (10, pos - 10); - auto val = rule.substr (pos + 1); - if ((val == "none" && ! task.has (uda)) || - task.get (uda) == val) - applyColor (base, c, merge); + auto pos = rule.find('.', 10); + if (pos == std::string::npos) { + if (task.has(rule.substr(10))) applyColor(base, c, merge); + } else { + auto uda = rule.substr(10, pos - 10); + auto val = rule.substr(pos + 1); + if ((val == "none" && !task.has(uda)) || task.get(uda) == val) applyColor(base, c, merge); } } //////////////////////////////////////////////////////////////////////////////// -static void colorizeDue (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.is_due ()) - applyColor (base, c, merge); +static void colorizeDue(Task& task, const Color& base, Color& c, bool merge) { + if (task.is_due()) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeDueToday (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.is_duetoday ()) - applyColor (base, c, merge); +static void colorizeDueToday(Task& task, const Color& base, Color& c, bool merge) { + if (task.is_duetoday()) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeOverdue (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.is_overdue ()) - applyColor (base, c, merge); +static void colorizeOverdue(Task& task, const Color& base, Color& c, bool merge) { + if (task.is_overdue()) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeRecurring (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.has ("recur")) - applyColor (base, c, merge); +static void colorizeRecurring(Task& task, const Color& base, Color& c, bool merge) { + if (task.has("recur")) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeCompleted (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.getStatus () == Task::completed) - applyColor (base, c, merge); +static void colorizeCompleted(Task& task, const Color& base, Color& c, bool merge) { + if (task.getStatus() == Task::completed) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -static void colorizeDeleted (Task& task, const Color& base, Color& c, bool merge) -{ - if (task.getStatus () == Task::deleted) - applyColor (base, c, merge); +static void colorizeDeleted(Task& task, const Color& base, Color& c, bool merge) { + if (task.getStatus() == Task::deleted) applyColor(base, c, merge); } //////////////////////////////////////////////////////////////////////////////// -void autoColorize (Task& task, Color& c) -{ +void autoColorize(Task& task, Color& c) { // The special tag 'nocolor' overrides all auto and specific colorization. - if (! Context::getContext ().color () || - task.hasTag ("nocolor")) - { - c = Color (); + if (!Context::getContext().color() || task.hasTag("nocolor")) { + c = Color(); return; } - auto merge = Context::getContext ().config.getBoolean ("rule.color.merge"); + auto merge = Context::getContext().config.getBoolean("rule.color.merge"); // Note: c already contains colors specifically assigned via command. // Note: These rules form a hierarchy - the last rule is King, hence the // reverse iterator. - for (auto r = gsPrecedence.rbegin (); r != gsPrecedence.rend (); ++r) - { + for (auto r = gsPrecedence.rbegin(); r != gsPrecedence.rend(); ++r) { Color base = gsColor[*r]; - if (base.nontrivial ()) - { - if (*r == "color.blocked") colorizeBlocked (task, base, c, merge); - else if (*r == "color.blocking") colorizeBlocking (task, base, c, merge); - else if (*r == "color.tagged") colorizeTagged (task, base, c, merge); - else if (*r == "color.active") colorizeActive (task, base, c, merge); - else if (*r == "color.scheduled") colorizeScheduled (task, base, c, merge); - else if (*r == "color.until") colorizeUntil (task, base, c, merge); - else if (*r == "color.project.none") colorizeProjectNone (task, base, c, merge); - else if (*r == "color.tag.none") colorizeTagNone (task, base, c, merge); - else if (*r == "color.due") colorizeDue (task, base, c, merge); - else if (*r == "color.due.today") colorizeDueToday (task, base, c, merge); - else if (*r == "color.overdue") colorizeOverdue (task, base, c, merge); - else if (*r == "color.recurring") colorizeRecurring (task, base, c, merge); - else if (*r == "color.completed") colorizeCompleted (task, base, c, merge); - else if (*r == "color.deleted") colorizeDeleted (task, base, c, merge); + if (base.nontrivial()) { + if (*r == "color.blocked") + colorizeBlocked(task, base, c, merge); + else if (*r == "color.blocking") + colorizeBlocking(task, base, c, merge); + else if (*r == "color.tagged") + colorizeTagged(task, base, c, merge); + else if (*r == "color.active") + colorizeActive(task, base, c, merge); + else if (*r == "color.scheduled") + colorizeScheduled(task, base, c, merge); + else if (*r == "color.until") + colorizeUntil(task, base, c, merge); + else if (*r == "color.project.none") + colorizeProjectNone(task, base, c, merge); + else if (*r == "color.tag.none") + colorizeTagNone(task, base, c, merge); + else if (*r == "color.due") + colorizeDue(task, base, c, merge); + else if (*r == "color.due.today") + colorizeDueToday(task, base, c, merge); + else if (*r == "color.overdue") + colorizeOverdue(task, base, c, merge); + else if (*r == "color.recurring") + colorizeRecurring(task, base, c, merge); + else if (*r == "color.completed") + colorizeCompleted(task, base, c, merge); + else if (*r == "color.deleted") + colorizeDeleted(task, base, c, merge); // Wildcards - else if (! r->compare (0, 10, "color.tag.", 10)) colorizeTag (task, *r, base, c, merge); - else if (! r->compare (0, 14, "color.project.", 14)) colorizeProject (task, *r, base, c, merge); - else if (! r->compare (0, 14, "color.keyword.", 14)) colorizeKeyword (task, *r, base, c, merge); - else if (! r->compare (0, 10, "color.uda.", 10)) colorizeUDA (task, *r, base, c, merge); + else if (!r->compare(0, 10, "color.tag.", 10)) + colorizeTag(task, *r, base, c, merge); + else if (!r->compare(0, 14, "color.project.", 14)) + colorizeProject(task, *r, base, c, merge); + else if (!r->compare(0, 14, "color.keyword.", 14)) + colorizeKeyword(task, *r, base, c, merge); + else if (!r->compare(0, 10, "color.uda.", 10)) + colorizeUDA(task, *r, base, c, merge); } } - } //////////////////////////////////////////////////////////////////////////////// -std::string colorizeHeader (const std::string& input) -{ - if (gsColor["color.header"].nontrivial ()) - return gsColor["color.header"].colorize (input); +std::string colorizeHeader(const std::string& input) { + if (gsColor["color.header"].nontrivial()) return gsColor["color.header"].colorize(input); return input; } //////////////////////////////////////////////////////////////////////////////// -std::string colorizeFootnote (const std::string& input) -{ - if (gsColor["color.footnote"].nontrivial ()) - return gsColor["color.footnote"].colorize (input); +std::string colorizeFootnote(const std::string& input) { + if (gsColor["color.footnote"].nontrivial()) return gsColor["color.footnote"].colorize(input); return input; } //////////////////////////////////////////////////////////////////////////////// -std::string colorizeError (const std::string& input) -{ - if (gsColor["color.error"].nontrivial ()) - return gsColor["color.error"].colorize (input); +std::string colorizeError(const std::string& input) { + if (gsColor["color.error"].nontrivial()) return gsColor["color.error"].colorize(input); return input; } //////////////////////////////////////////////////////////////////////////////// -std::string colorizeDebug (const std::string& input) -{ - if (gsColor["color.debug"].nontrivial ()) - return gsColor["color.debug"].colorize (input); +std::string colorizeDebug(const std::string& input) { + if (gsColor["color.debug"].nontrivial()) return gsColor["color.debug"].colorize(input); return input; } diff --git a/src/rules.h b/src/rules.h new file mode 100644 index 000000000..d0d8c7f85 --- /dev/null +++ b/src/rules.h @@ -0,0 +1,47 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef INCLUDED_RULES +#define INCLUDED_RULES + +#include +// cmake.h include header must come first + +#include +#include +#include + +void initializeColorRules(); +void autoColorize(Task& task, Color& c); +std::string colorizeHeader(const std::string& input); +std::string colorizeFootnote(const std::string& input); +std::string colorizeError(const std::string& input); +std::string colorizeDebug(const std::string& input); + +#endif + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/sort.cpp b/src/sort.cpp index ac5c3d418..4b0bee350 100644 --- a/src/sort.cpp +++ b/src/sort.cpp @@ -25,93 +25,79 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include -#include -#include -#include -#include +// cmake.h include header must come first + #include #include #include -#include -#include #include +#include +#include +#include -static std::vector * global_data = nullptr; -static std::vector global_keys; -static bool sort_compare (int, int); +#include +#include +#include +#include +#include + +static std::vector* global_data = nullptr; +static std::vector global_keys; +static bool sort_compare(int, int); //////////////////////////////////////////////////////////////////////////////// -void sort_tasks ( - std::vector & data, - std::vector & order, - const std::string& keys) -{ +void sort_tasks(std::vector& data, std::vector& order, const std::string& keys) { Timer timer; global_data = &data; // Split the key defs. - global_keys = split (keys, ','); + global_keys = split(keys, ','); // Only sort if necessary. - if (order.size ()) - std::stable_sort (order.begin (), order.end (), sort_compare); + if (order.size()) std::stable_sort(order.begin(), order.end(), sort_compare); - Context::getContext ().time_sort_us += timer.total_us (); + Context::getContext().time_sort_us += timer.total_us(); } -void sort_projects ( - std::list >& sorted, - std::map & allProjects) -{ - for (auto& project : allProjects) - { - const std::vector parents = extractParents (project.first); - if (parents.size ()) - { +void sort_projects(std::list>& sorted, + std::map& allProjects) { + for (auto& project : allProjects) { + const std::vector parents = extractParents(project.first); + if (parents.size()) { // if parents exist: store iterator position of last parent - std::list >::iterator parent_pos; - for (auto& parent : parents) - { - parent_pos = std::find_if (sorted.begin (), sorted.end (), - [&parent](const std::pair & item) { return item.first == parent; }); - + std::list>::iterator parent_pos; + for (auto& parent : parents) { + parent_pos = std::find_if( + sorted.begin(), sorted.end(), + [&parent](const std::pair& item) { return item.first == parent; }); + // if parent does not exist yet: insert into sorted view - if (parent_pos == sorted.end ()) - sorted.emplace_back (parent, 1); + if (parent_pos == sorted.end()) sorted.emplace_back(parent, 1); } - + // insert new element below latest parent - sorted.insert ((parent_pos == sorted.end ()) ? parent_pos : ++parent_pos, project); - } - else - { + sorted.insert((parent_pos == sorted.end()) ? parent_pos : ++parent_pos, project); + } else { // if has no parents: simply push to end of list - sorted.push_back (project); + sorted.push_back(project); } } } -void sort_projects ( - std::list >& sorted, - std::map & allProjects) -{ - std::map allProjectsInt; - for (auto& p : allProjects) - allProjectsInt[p.first] = (int) p.second; - - sort_projects (sorted, allProjectsInt); -} +void sort_projects(std::list>& sorted, + std::map& allProjects) { + std::map allProjectsInt; + for (auto& p : allProjects) allProjectsInt[p.first] = (int)p.second; + sort_projects(sorted, allProjectsInt); +} //////////////////////////////////////////////////////////////////////////////// // Re-implementation, using direct Task access instead of data copies that // require re-parsing. // // Essentially a static implementation of a dynamic operator<. -static bool sort_compare (int left, int right) -{ +static bool sort_compare(int left, int right) { std::string field; bool ascending; bool breakIndicator; @@ -121,205 +107,148 @@ static bool sort_compare (int left, int right) float left_real; float right_real; - for (auto& k : global_keys) - { - Context::getContext ().decomposeSortField (k, field, ascending, breakIndicator); + for (auto& k : global_keys) { + Context::getContext().decomposeSortField(k, field, ascending, breakIndicator); // Urgency. - if (field == "urgency") - { - left_real = (*global_data)[left].urgency (); - right_real = (*global_data)[right].urgency (); + if (field == "urgency") { + left_real = (*global_data)[left].urgency(); + right_real = (*global_data)[right].urgency(); - if (left_real == right_real) - continue; + if (left_real == right_real) continue; - return ascending ? (left_real < right_real) - : (left_real > right_real); + return ascending ? (left_real < right_real) : (left_real > right_real); } // Number. - else if (field == "id") - { - left_number = (*global_data)[left].id; + else if (field == "id") { + left_number = (*global_data)[left].id; right_number = (*global_data)[right].id; - if (left_number == right_number) - continue; + if (left_number == right_number) continue; - return ascending ? (left_number < right_number) - : (left_number > right_number); + return ascending ? (left_number < right_number) : (left_number > right_number); } // String. - else if (field == "description" || - field == "project" || - field == "status" || - field == "tags" || - field == "uuid" || - field == "parent" || - field == "imask" || - field == "mask") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); + else if (field == "description" || field == "project" || field == "status" || field == "tags" || + field == "uuid" || field == "parent" || field == "imask" || field == "mask") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); - if (left_string == right_string) - continue; + if (left_string == right_string) continue; - return ascending ? (left_string < right_string) - : (left_string > right_string); + return ascending ? (left_string < right_string) : (left_string > right_string); } // Due Date. - else if (field == "due" || - field == "end" || - field == "entry" || - field == "start" || - field == "until" || - field == "wait" || - field == "modified" || - field == "scheduled") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); + else if (field == "due" || field == "end" || field == "entry" || field == "start" || + field == "until" || field == "wait" || field == "modified" || field == "scheduled") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); - if (left_string != "" && right_string == "") - return true; + if (left_string != "" && right_string == "") return true; - if (left_string == "" && right_string != "") - return false; + if (left_string == "" && right_string != "") return false; - if (left_string == right_string) - continue; + if (left_string == right_string) continue; - return ascending ? (left_string < right_string) - : (left_string > right_string); + return ascending ? (left_string < right_string) : (left_string > right_string); } // Depends string. - else if (field == "depends") - { + else if (field == "depends") { // Raw data is an un-sorted list of UUIDs. We just need a stable // sort, so we sort them lexically. - auto left_deps = (*global_data)[left].getDependencyUUIDs (); + auto left_deps = (*global_data)[left].getDependencyUUIDs(); std::sort(left_deps.begin(), left_deps.end()); - auto right_deps = (*global_data)[right].getDependencyUUIDs (); + auto right_deps = (*global_data)[right].getDependencyUUIDs(); std::sort(right_deps.begin(), right_deps.end()); - if (left_deps == right_deps) - continue; + if (left_deps == right_deps) continue; - if (left_deps.size () == 0 && right_deps.size () > 0) - return ascending; + if (left_deps.size() == 0 && right_deps.size() > 0) return ascending; - if (left_deps.size () > 0 && right_deps.size () == 0) - return !ascending; + if (left_deps.size() > 0 && right_deps.size() == 0) return !ascending; // Sort on the first dependency. - left_number = Context::getContext ().tdb2.id (left_deps[0]); - right_number = Context::getContext ().tdb2.id (right_deps[0]); + left_number = Context::getContext().tdb2.id(left_deps[0]); + right_number = Context::getContext().tdb2.id(right_deps[0]); - if (left_number == right_number) - continue; + if (left_number == right_number) continue; - return ascending ? (left_number < right_number) - : (left_number > right_number); + return ascending ? (left_number < right_number) : (left_number > right_number); } // Duration. - else if (field == "recur") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); + else if (field == "recur") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); - if (left_string == right_string) - continue; + if (left_string == right_string) continue; - Duration left_duration (left_string); - Duration right_duration (right_string); - return ascending ? (left_duration < right_duration) - : (left_duration > right_duration); + Duration left_duration(left_string); + Duration right_duration(right_string); + return ascending ? (left_duration < right_duration) : (left_duration > right_duration); } // UDAs. - else if ((column = Context::getContext ().columns[field]) != nullptr) - { - std::string type = column->type (); - if (type == "numeric") - { - auto left_real = strtof (((*global_data)[left].get_ref (field)).c_str (), nullptr); - auto right_real = strtof (((*global_data)[right].get_ref (field)).c_str (), nullptr); + else if ((column = Context::getContext().columns[field]) != nullptr) { + std::string type = column->type(); + if (type == "numeric") { + auto left_real = strtof(((*global_data)[left].get_ref(field)).c_str(), nullptr); + auto right_real = strtof(((*global_data)[right].get_ref(field)).c_str(), nullptr); - if (left_real == right_real) - continue; + if (left_real == right_real) continue; - return ascending ? (left_real < right_real) - : (left_real > right_real); - } - else if (type == "string") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); + return ascending ? (left_real < right_real) : (left_real > right_real); + } else if (type == "string") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); - if (left_string == right_string) - continue; + if (left_string == right_string) continue; // UDAs of the type string can have custom sort orders, which need to be considered. - auto order = Task::customOrder.find (field); - if (order != Task::customOrder.end ()) - { + auto order = Task::customOrder.find(field); + if (order != Task::customOrder.end()) { // Guaranteed to be found, because of ColUDA::validate (). - auto posLeft = std::find (order->second.begin (), order->second.end (), left_string); - auto posRight = std::find (order->second.begin (), order->second.end (), right_string); + auto posLeft = std::find(order->second.begin(), order->second.end(), left_string); + auto posRight = std::find(order->second.begin(), order->second.end(), right_string); return ascending ? (posLeft < posRight) : (posLeft > posRight); - } - else - { + } else { // Empty values are unconditionally last, if no custom order was specified. if (left_string == "") return false; else if (right_string == "") return true; - return ascending ? (left_string < right_string) - : (left_string > right_string); + return ascending ? (left_string < right_string) : (left_string > right_string); } } - else if (type == "date") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); + else if (type == "date") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); - if (left_string != "" && right_string == "") - return true; + if (left_string != "" && right_string == "") return true; - if (left_string == "" && right_string != "") - return false; + if (left_string == "" && right_string != "") return false; - if (left_string == right_string) - continue; + if (left_string == right_string) continue; - return ascending ? (left_string < right_string) - : (left_string > right_string); + return ascending ? (left_string < right_string) : (left_string > right_string); + } else if (type == "duration") { + auto left_string = (*global_data)[left].get_ref(field); + auto right_string = (*global_data)[right].get_ref(field); + + if (left_string == right_string) continue; + + Duration left_duration(left_string); + Duration right_duration(right_string); + return ascending ? (left_duration < right_duration) : (left_duration > right_duration); } - else if (type == "duration") - { - auto left_string = (*global_data)[left].get_ref (field); - auto right_string = (*global_data)[right].get_ref (field); - - if (left_string == right_string) - continue; - - Duration left_duration (left_string); - Duration right_duration (right_string); - return ascending ? (left_duration < right_duration) - : (left_duration > right_duration); - } - } - else - throw format ("The '{1}' column is not a valid sort field.", field); + } else + throw format("The '{1}' column is not a valid sort field.", field); } return false; diff --git a/src/tc/util.h b/src/sort.h similarity index 67% rename from src/tc/util.h rename to src/sort.h index 66ca54b2e..6972f9c2e 100644 --- a/src/tc/util.h +++ b/src/sort.h @@ -1,6 +1,7 @@ //////////////////////////////////////////////////////////////////////////////// // -// Copyright 2022, Dustin J. Mitchell +// Copyright 2006 - 2025, Tomas Babej, Paul Beckingham, Federico Hernandez, +// Tobias Predel. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,28 +25,27 @@ // //////////////////////////////////////////////////////////////////////////////// -#ifndef INCLUDED_TC_UTIL -#define INCLUDED_TC_UTIL +#ifndef INCLUDED_SORT +#define INCLUDED_SORT +#include +// cmake.h include header must come first + +#include + +#include +#include #include -#include "tc/ffi.h" +#include -namespace tc { - // convert a std::string into a TCString, copying the contained data - tc::ffi::TCString string2tc(const std::string&); +void sort_tasks(std::vector& data, std::vector& order, const std::string& keys); - // convert a TCString into a std::string, leaving the TCString as-is - std::string tc2string_clone(const tc::ffi::TCString&); +void sort_projects(std::list>& sorted, + std::map& allProjects); - // convert a TCString into a std::string, freeing the TCString - std::string tc2string(tc::ffi::TCString&); - - // convert a TCUuid into a std::string - std::string tc2uuid(tc::ffi::TCUuid&); - - // parse a std::string into a TCUuid (throwing if parse fails) - tc::ffi::TCUuid uuid2tc(const std::string&); -} +void sort_projects(std::list>& sorted, + std::map& allProjects); #endif + //////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/.gitignore b/src/taskchampion-cpp/.gitignore similarity index 100% rename from src/tc/.gitignore rename to src/taskchampion-cpp/.gitignore diff --git a/src/taskchampion-cpp/CMakeLists.txt b/src/taskchampion-cpp/CMakeLists.txt new file mode 100644 index 000000000..a0d4a42ab --- /dev/null +++ b/src/taskchampion-cpp/CMakeLists.txt @@ -0,0 +1,29 @@ +cmake_minimum_required (VERSION 3.22) + +OPTION(SYSTEM_CORROSION "Use system provided corrosion instead of vendored version" OFF) +if(SYSTEM_CORROSION) + find_package(Corrosion REQUIRED) +else() + add_subdirectory(${CMAKE_SOURCE_DIR}/src/taskchampion-cpp/corrosion) +endif() + +OPTION (ENABLE_TLS_NATIVE_ROOTS "Use the system's TLS root certificates" OFF) + +if (ENABLE_TLS_NATIVE_ROOTS) + message ("Enabling native TLS roots") + set(TASKCHAMPION_FEATURES "tls-native-roots") +endif (ENABLE_TLS_NATIVE_ROOTS) + +# Import taskchampion-lib as a CMake library. This implements the Rust side of +# the cxxbridge, and depends on the `taskchampion` crate. +corrosion_import_crate( + MANIFEST_PATH "${CMAKE_SOURCE_DIR}/Cargo.toml" + LOCKED + CRATES "taskchampion-lib" + FEATURES "${TASKCHAMPION_FEATURES}") + +# Set up `taskchampion-cpp`, the C++ side of the bridge. +corrosion_add_cxxbridge(taskchampion-cpp + CRATE taskchampion_lib + FILES lib.rs +) diff --git a/src/taskchampion-cpp/Cargo.toml b/src/taskchampion-cpp/Cargo.toml new file mode 100644 index 000000000..13ee8920d --- /dev/null +++ b/src/taskchampion-cpp/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "taskchampion-lib" +version = "0.1.0" +edition = "2021" +publish = false +rust-version = "1.81.0" # MSRV + +[lib] +crate-type = ["staticlib"] + +[dependencies] +taskchampion = "=2.0.2" +cxx = "1.0.133" + +[features] +# use native CA roots, instead of bundled +tls-native-roots = ["taskchampion/tls-native-roots"] + +[build-dependencies] +cxx-build = "1.0.133" diff --git a/src/taskchampion-cpp/build.rs b/src/taskchampion-cpp/build.rs new file mode 100644 index 000000000..7ad128819 --- /dev/null +++ b/src/taskchampion-cpp/build.rs @@ -0,0 +1,6 @@ +#[allow(unused_must_use)] +fn main() { + cxx_build::bridge("src/lib.rs"); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/lib.rs"); +} diff --git a/src/taskchampion-cpp/corrosion b/src/taskchampion-cpp/corrosion new file mode 160000 index 000000000..4eccadd67 --- /dev/null +++ b/src/taskchampion-cpp/corrosion @@ -0,0 +1 @@ +Subproject commit 4eccadd67819b427978ca540e0c31e6cce08f226 diff --git a/src/taskchampion-cpp/src/lib.rs b/src/taskchampion-cpp/src/lib.rs new file mode 100644 index 000000000..45a146edc --- /dev/null +++ b/src/taskchampion-cpp/src/lib.rs @@ -0,0 +1,1111 @@ +use cxx::CxxString; +use std::path::PathBuf; +use std::pin::Pin; +use taskchampion as tc; + +// All Taskchampion FFI is contained in this module, due to issues with cxx and multiple modules +// such as https://github.com/dtolnay/cxx/issues/1323. + +/// FFI interface for TaskChampion. +/// +/// This loosely follows the TaskChampion API defined at +/// https://docs.rs/taskchampion/latest/taskchampion/, with adjustments made as necessary to +/// accomodate cxx's limitations. Consult that documentation for full descriptions of the types and +/// methods. +/// +/// This interface is an internal implementation detail of Taskwarrior and may change at any time. +#[cxx::bridge(namespace = "tc")] +mod ffi { + // --- Uuid + + #[derive(Debug, Eq, PartialEq, Clone, Copy)] + struct Uuid { + v: [u8; 16], + } + + extern "Rust" { + /// Generate a new, random Uuid. + fn uuid_v4() -> Uuid; + + /// Parse the given string as a Uuid, panicking if it is not valid. + fn uuid_from_string(uuid: Pin<&CxxString>) -> Uuid; + + /// Convert the given Uuid to a string. + fn to_string(self: &Uuid) -> String; + + /// Check whether this is the "nil" Uuid, used as a sentinel value. + fn is_nil(self: &Uuid) -> bool; + } + + // --- Operation and Operations + + extern "Rust" { + type Operation; + + /// Check if this is a Create operation. + fn is_create(&self) -> bool; + + /// Check if this is a Update operation. + fn is_update(&self) -> bool; + + /// Check if this is a Delete operation. + fn is_delete(&self) -> bool; + + /// Check if this is an UndoPoint operation. + fn is_undo_point(&self) -> bool; + + /// Get the operation's uuid. + /// + /// Only valid for create, update, and delete operations. + fn get_uuid(&self) -> Uuid; + + /// Get the `old_task` for this update operation. + /// + /// Only valid for delete operations. + fn get_old_task(&self) -> Vec; + + /// Get the `property` for this update operation. + /// + /// Only valid for update operations. + fn get_property(&self, property_out: Pin<&mut CxxString>); + + /// Get the `value` for this update operation, returning false if the + /// `value` field is None. + /// + /// Only valid for update operations. + fn get_value(&self, value_out: Pin<&mut CxxString>) -> bool; + + /// Get the `old_value` for this update operation, returning false if the + /// `old_value` field is None. + /// + /// Only valid for update operations. + fn get_old_value(&self, old_value_out: Pin<&mut CxxString>) -> bool; + + /// Get the `timestamp` for this update operation. + /// + /// Only valid for update operations. + fn get_timestamp(&self) -> i64; + + /// Create a new vector of operations. It's also fine to construct a + /// `rust::Vec` directly. + fn new_operations() -> Vec; + + /// Add an UndoPoint operation to the vector of operations. All other + /// operation types should be added via `TaskData`. + fn add_undo_point(ops: &mut Vec); + } + + // --- Replica + + extern "Rust" { + type Replica; + + /// Create a new in-memory replica, such as for testing. + fn new_replica_in_memory() -> Result>; + + /// Create a new replica stored on-disk. + fn new_replica_on_disk( + taskdb_dir: String, + create_if_missing: bool, + read_write: bool, + ) -> Result>; + + /// Commit the given operations to the replica. + fn commit_operations(&mut self, ops: Vec) -> Result<()>; + + /// Commit the reverse of the given operations. + fn commit_reversed_operations(&mut self, ops: Vec) -> Result; + + /// Get `TaskData` values for all tasks in the replica. + /// + /// This contains `OptionTaskData` to allow C++ to `take` values out of the vector and use + /// them as `rust::Box`. Cxx does not support `Vec>`. Cxx also does not + /// handle `HashMap`, so the result is not a map from uuid to task. The returned Vec is + /// fully populated, so it is safe to call `take` on each value in the returned Vec once . + fn all_task_data(&mut self) -> Result>; + + /// Simiar to all_task_data, but returing only pending tasks (those in the working set). + fn pending_task_data(&mut self) -> Result>; + + /// Get the UUIDs of all tasks. + fn all_task_uuids(&mut self) -> Result>; + + /// Expire old, deleted tasks. + fn expire_tasks(&mut self) -> Result<()>; + + /// Get an existing task by its UUID. + fn get_task_data(&mut self, uuid: Uuid) -> Result; + + /// Get the operations for a task task by its UUID. + fn get_task_operations(&mut self, uuid: Uuid) -> Result>; + + /// Return the operations back to and including the last undo point, or since the last sync if + /// no undo point is found. + fn get_undo_operations(&mut self) -> Result>; + + /// Get the number of local, un-sync'd operations, excluding undo operations. + fn num_local_operations(&mut self) -> Result; + + /// Get the number of (un-synchronized) undo points in storage. + fn num_undo_points(&mut self) -> Result; + + /// Rebuild the working set. + fn rebuild_working_set(&mut self, renumber: bool) -> Result<()>; + + /// Get the working set for this replica. + fn working_set(&mut self) -> Result>; + + /// Sync with a server crated from `ServerConfig::Local`. + fn sync_to_local(&mut self, server_dir: String, avoid_snapshots: bool) -> Result<()>; + + /// Sync with a server created from `ServerConfig::Remote`. + fn sync_to_remote( + &mut self, + url: String, + client_id: Uuid, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<()>; + + /// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Profile`. + fn sync_to_aws_with_profile( + &mut self, + region: String, + bucket: String, + profile_name: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<()>; + + /// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::AccessKey`. + fn sync_to_aws_with_access_key( + &mut self, + region: String, + bucket: String, + access_key_id: String, + secret_access_key: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<()>; + + /// Sync with a server created from `ServerConfig::Aws` using `AwsCredentials::Default`. + fn sync_to_aws_with_default_creds( + &mut self, + region: String, + bucket: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<()>; + + /// Sync with a server created from `ServerConfig::Gcp`. + /// + /// An empty value for `credential_path` is converted to `Option::None`. + fn sync_to_gcp( + &mut self, + bucket: String, + credential_path: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<()>; + } + + // --- OptionTaskData + + /// Wrapper around `Option>`, required since cxx does not support Option. + /// + /// Note that if an OptionTaskData containing a task is dropped without calling `take`, + /// it will leak the contained task. C++ code should be careful to always take. + struct OptionTaskData { + maybe_task: *mut TaskData, + } + + extern "Rust" { + /// Check if the value contains a task. + fn is_some(self: &OptionTaskData) -> bool; + /// Check if the value does not contain a task. + fn is_none(self: &OptionTaskData) -> bool; + /// Get the contained task, or panic if there is no task. The OptionTaskData + /// will be reset to contain None. + fn take(self: &mut OptionTaskData) -> Box; + } + + // --- TaskData + + extern "Rust" { + type TaskData; + + /// Create a new task with the given Uuid. + fn create_task(uuid: Uuid, ops: &mut Vec) -> Box; + + /// Get the task's Uuid. + fn get_uuid(&self) -> Uuid; + + /// Get a value on this task. If the property exists, returns true and updates + /// the output parameter. If not, returns false. + fn get(&self, property: &CxxString, value_out: Pin<&mut CxxString>) -> bool; + + /// Check if the given property is set. + fn has(&self, property: &CxxString) -> bool; + + /// Enumerate all properties on this task, in arbitrary order. + fn properties(&self) -> Vec; + + /// Enumerate all properties and their values on this task, in arbitrary order, as a + /// vector. + fn items(&self) -> Vec; + + /// Update the given property with the given value. + fn update(&mut self, property: &CxxString, value: &CxxString, ops: &mut Vec); + + /// Like `update`, but removing the property by passing None for the value. + fn update_remove(&mut self, property: &CxxString, ops: &mut Vec); + + /// Delete the task. The name is `delete_task` because `delete` is a C++ keyword. + fn delete_task(&mut self, ops: &mut Vec); + } + + // --- PropValuePair + + #[derive(Debug, Eq, PartialEq)] + struct PropValuePair { + prop: String, + value: String, + } + + // --- WorkingSet + + extern "Rust" { + type WorkingSet; + + /// Get the "length" of the working set: the total number of uuids in the set. + fn len(&self) -> usize; + + /// Get the largest index in the working set, or zero if the set is empty. + fn largest_index(&self) -> usize; + + /// True if the length is zero + fn is_empty(&self) -> bool; + + /// Get the uuid with the given index, if any exists. Returns the nil UUID if + /// there is no task at that index. + fn by_index(&self, index: usize) -> Uuid; + + /// Get the index for the given uuid, or zero if it is not in the working set. + fn by_uuid(&self, uuid: Uuid) -> usize; + + /// Get the entire working set, as a vector indexed by each task's id. For example, the + /// UUID for task 5 will be at `all_uuids()[5]`. All elements of the vector not corresponding + /// to a task contain the nil UUID. + fn all_uuids(&self) -> Vec; + } +} + +#[derive(Debug)] +struct CppError(tc::Error); + +impl From for CppError { + fn from(err: tc::Error) -> Self { + CppError(err) + } +} + +impl std::fmt::Display for CppError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let tc::Error::Other(err) = &self.0 { + // The default `to_string` representation of `anyhow::Error` only shows the "outermost" + // context, e.g., "Could not connect to server", and omits the juicy details about what + // actually went wrong. So, join all of those contexts with `: ` for presentation to the C++ + // layer. + let entire_msg = err + .chain() + .skip(1) + .fold(err.to_string(), |a, b| format!("{}: {}", a, b)); + write!(f, "{}", entire_msg) + } else { + self.0.fmt(f) + } + } +} + +// --- Uuid + +impl From for tc::Uuid { + fn from(value: ffi::Uuid) -> Self { + tc::Uuid::from_bytes(value.v) + } +} + +impl From<&ffi::Uuid> for tc::Uuid { + fn from(value: &ffi::Uuid) -> Self { + tc::Uuid::from_bytes(value.v) + } +} + +impl From for ffi::Uuid { + fn from(uuid: tc::Uuid) -> ffi::Uuid { + ffi::Uuid { + v: *uuid.as_bytes(), + } + } +} + +impl From<&tc::Uuid> for ffi::Uuid { + fn from(uuid: &tc::Uuid) -> ffi::Uuid { + ffi::Uuid { + v: *uuid.as_bytes(), + } + } +} + +fn uuid_v4() -> ffi::Uuid { + tc::Uuid::new_v4().into() +} + +fn uuid_from_string(uuid: Pin<&CxxString>) -> ffi::Uuid { + let Ok(uuid) = tc::Uuid::parse_str(uuid.to_str().expect("invalid utf-8")) else { + panic!("{} is not a valid UUID", uuid); + }; + uuid.into() +} + +impl ffi::Uuid { + #[allow(clippy::inherent_to_string, clippy::wrong_self_convention)] + fn to_string(&self) -> String { + tc::Uuid::from(self).as_hyphenated().to_string() + } + + fn is_nil(&self) -> bool { + tc::Uuid::from(self).is_nil() + } +} + +// --- Operation and Operations + +#[repr(transparent)] // required for safety +pub struct Operation(tc::Operation); + +impl Operation { + fn is_create(&self) -> bool { + matches!(&self.0, tc::Operation::Create { .. }) + } + + fn is_update(&self) -> bool { + matches!(&self.0, tc::Operation::Update { .. }) + } + + fn is_delete(&self) -> bool { + matches!(&self.0, tc::Operation::Delete { .. }) + } + + fn is_undo_point(&self) -> bool { + matches!(&self.0, tc::Operation::UndoPoint) + } + + fn get_uuid(&self) -> ffi::Uuid { + match self.0 { + tc::Operation::Create { uuid, .. } => uuid, + tc::Operation::Update { uuid, .. } => uuid, + tc::Operation::Delete { uuid, .. } => uuid, + _ => panic!("operation has no uuid"), + } + .into() + } + + fn get_property(&self, mut property_out: Pin<&mut CxxString>) { + match &self.0 { + tc::Operation::Update { property, .. } => { + property_out.as_mut().clear(); + property_out.as_mut().push_str(property); + } + _ => panic!("operation is not an update"), + } + } + + fn get_value(&self, mut value_out: Pin<&mut CxxString>) -> bool { + match &self.0 { + tc::Operation::Update { value, .. } => { + if let Some(value) = value { + value_out.as_mut().clear(); + value_out.as_mut().push_str(value); + true + } else { + false + } + } + _ => panic!("operation is not an update"), + } + } + + fn get_old_value(&self, mut old_value_out: Pin<&mut CxxString>) -> bool { + match &self.0 { + tc::Operation::Update { old_value, .. } => { + if let Some(old_value) = old_value { + old_value_out.as_mut().clear(); + old_value_out.as_mut().push_str(old_value); + true + } else { + false + } + } + _ => panic!("operation is not an update"), + } + } + + fn get_timestamp(&self) -> i64 { + match &self.0 { + tc::Operation::Update { timestamp, .. } => timestamp.timestamp(), + _ => panic!("operation is not an update"), + } + } + + fn get_old_task(&self) -> Vec { + match &self.0 { + tc::Operation::Delete { old_task, .. } => old_task + .iter() + .map(|(p, v)| ffi::PropValuePair { + prop: p.into(), + value: v.into(), + }) + .collect(), + _ => panic!("operation is not a delete"), + } + } +} + +fn new_operations() -> Vec { + Vec::new() +} + +fn add_undo_point(ops: &mut Vec) { + ops.push(Operation(tc::Operation::UndoPoint)); +} + +// --- Replica + +struct Replica(tc::Replica); + +impl From for Replica { + fn from(inner: tc::Replica) -> Self { + Replica(inner) + } +} + +fn new_replica_on_disk( + taskdb_dir: String, + create_if_missing: bool, + read_write: bool, +) -> Result, CppError> { + use tc::storage::AccessMode::*; + let access_mode = if read_write { ReadWrite } else { ReadOnly }; + let storage = tc::StorageConfig::OnDisk { + taskdb_dir: PathBuf::from(taskdb_dir), + create_if_missing, + access_mode, + } + .into_storage()?; + Ok(Box::new(tc::Replica::new(storage).into())) +} + +fn new_replica_in_memory() -> Result, CppError> { + let storage = tc::StorageConfig::InMemory.into_storage()?; + Ok(Box::new(tc::Replica::new(storage).into())) +} + +/// Utility function for Replica methods using Operations. +fn to_tc_operations(ops: Vec) -> Vec { + // SAFETY: Operation is a transparent newtype for tc::Operation, so a Vec of one is + // a Vec of the other. + unsafe { std::mem::transmute::, Vec>(ops) } +} + +/// Utility function for Replica methods using Operations. +fn from_tc_operations(ops: Vec) -> Vec { + // SAFETY: Operation is a transparent newtype for tc::Operation, so a Vec of one is + // a Vec of the other. + unsafe { std::mem::transmute::, Vec>(ops) } +} + +impl Replica { + fn commit_operations(&mut self, ops: Vec) -> Result<(), CppError> { + Ok(self.0.commit_operations(to_tc_operations(ops))?) + } + + fn commit_reversed_operations(&mut self, ops: Vec) -> Result { + Ok(self.0.commit_reversed_operations(to_tc_operations(ops))?) + } + + fn all_task_data(&mut self) -> Result, CppError> { + Ok(self + .0 + .all_task_data()? + .drain() + .map(|(_, t)| Some(t).into()) + .collect()) + } + + fn pending_task_data(&mut self) -> Result, CppError> { + Ok(self + .0 + .pending_task_data()? + .drain(..) + .map(|t| Some(t).into()) + .collect()) + } + + fn all_task_uuids(&mut self) -> Result, CppError> { + Ok(self + .0 + .all_task_uuids()? + .into_iter() + .map(ffi::Uuid::from) + .collect()) + } + + fn expire_tasks(&mut self) -> Result<(), CppError> { + Ok(self.0.expire_tasks()?) + } + + fn get_task_data(&mut self, uuid: ffi::Uuid) -> Result { + Ok(self.0.get_task_data(uuid.into())?.into()) + } + + fn get_task_operations(&mut self, uuid: ffi::Uuid) -> Result, CppError> { + Ok(from_tc_operations(self.0.get_task_operations(uuid.into())?)) + } + + fn get_undo_operations(&mut self) -> Result, CppError> { + Ok(from_tc_operations(self.0.get_undo_operations()?)) + } + + fn num_local_operations(&mut self) -> Result { + Ok(self.0.num_local_operations()?) + } + + fn num_undo_points(&mut self) -> Result { + Ok(self.0.num_undo_points()?) + } + + fn rebuild_working_set(&mut self, renumber: bool) -> Result<(), CppError> { + Ok(self.0.rebuild_working_set(renumber)?) + } + + fn working_set(&mut self) -> Result, CppError> { + Ok(Box::new(self.0.working_set()?.into())) + } + + fn sync_to_local(&mut self, server_dir: String, avoid_snapshots: bool) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Local { + server_dir: server_dir.into(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } + + fn sync_to_remote( + &mut self, + url: String, + client_id: ffi::Uuid, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Remote { + url, + client_id: client_id.into(), + encryption_secret: encryption_secret.as_bytes().to_vec(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } + + fn sync_to_aws_with_profile( + &mut self, + region: String, + bucket: String, + profile_name: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Aws { + region, + bucket, + credentials: tc::server::AwsCredentials::Profile { profile_name }, + encryption_secret: encryption_secret.as_bytes().to_vec(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } + + fn sync_to_aws_with_access_key( + &mut self, + region: String, + bucket: String, + access_key_id: String, + secret_access_key: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Aws { + region, + bucket, + credentials: tc::server::AwsCredentials::AccessKey { + access_key_id, + secret_access_key, + }, + encryption_secret: encryption_secret.as_bytes().to_vec(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } + + fn sync_to_aws_with_default_creds( + &mut self, + region: String, + bucket: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Aws { + region, + bucket, + credentials: tc::server::AwsCredentials::Default, + encryption_secret: encryption_secret.as_bytes().to_vec(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } + + fn sync_to_gcp( + &mut self, + bucket: String, + credential_path: String, + encryption_secret: &CxxString, + avoid_snapshots: bool, + ) -> Result<(), CppError> { + let mut server = tc::server::ServerConfig::Gcp { + bucket, + credential_path: if credential_path.is_empty() { + None + } else { + Some(credential_path) + }, + encryption_secret: encryption_secret.as_bytes().to_vec(), + } + .into_server()?; + Ok(self.0.sync(&mut server, avoid_snapshots)?) + } +} + +// --- OptionTaskData + +impl From> for ffi::OptionTaskData { + fn from(value: Option) -> Self { + let Some(task) = value else { + return ffi::OptionTaskData { + maybe_task: std::ptr::null_mut(), + }; + }; + let boxed = Box::new(task.into()); + ffi::OptionTaskData { + maybe_task: Box::into_raw(boxed), + } + } +} + +impl ffi::OptionTaskData { + fn is_some(&self) -> bool { + !self.maybe_task.is_null() + } + + fn is_none(&self) -> bool { + self.maybe_task.is_null() + } + + fn take(&mut self) -> Box { + let mut ptr = std::ptr::null_mut(); + std::mem::swap(&mut ptr, &mut self.maybe_task); + if ptr.is_null() { + panic!("Cannot take an empty OptionTaskdata"); + } + // SAFETY: this value is not NULL and was created from `Box::into_raw` in the + // `From>` implementation above. + unsafe { Box::from_raw(ptr) } + } +} + +// --- TaskData + +pub struct TaskData(tc::TaskData); + +impl From for TaskData { + fn from(task: tc::TaskData) -> Self { + TaskData(task) + } +} + +/// Utility function for TaskData methods. +fn operations_ref(ops: &mut Vec) -> &mut Vec { + // SAFETY: Operation is a transparent newtype for tc::Operation, so a Vec of one is a + // Vec of the other. + unsafe { std::mem::transmute::<&mut Vec, &mut Vec>(ops) } +} + +fn create_task(uuid: ffi::Uuid, ops: &mut Vec) -> Box { + let t = tc::TaskData::create(uuid.into(), operations_ref(ops)); + Box::new(TaskData(t)) +} + +impl TaskData { + fn get_uuid(&self) -> ffi::Uuid { + self.0.get_uuid().into() + } + + fn get(&self, property: &CxxString, mut value_out: Pin<&mut CxxString>) -> bool { + let Some(value) = self.0.get(property.to_string_lossy()) else { + return false; + }; + value_out.as_mut().clear(); + value_out.as_mut().push_str(value); + true + } + + fn has(&self, property: &CxxString) -> bool { + self.0.has(property.to_string_lossy()) + } + + fn properties(&self) -> Vec { + self.0.properties().map(|s| s.to_owned()).collect() + } + + fn items(&self) -> Vec { + self.0 + .iter() + .map(|(p, v)| ffi::PropValuePair { + prop: p.into(), + value: v.into(), + }) + .collect() + } + + fn update(&mut self, property: &CxxString, value: &CxxString, ops: &mut Vec) { + self.0.update( + property.to_string_lossy(), + Some(value.to_string_lossy().into()), + operations_ref(ops), + ) + } + + fn update_remove(&mut self, property: &CxxString, ops: &mut Vec) { + self.0 + .update(property.to_string_lossy(), None, operations_ref(ops)) + } + + fn delete_task(&mut self, ops: &mut Vec) { + self.0.delete(operations_ref(ops)) + } +} + +// --- WorkingSet + +struct WorkingSet(tc::WorkingSet); + +impl From for WorkingSet { + fn from(task: tc::WorkingSet) -> Self { + WorkingSet(task) + } +} + +impl WorkingSet { + fn len(&self) -> usize { + self.0.len() + } + + fn largest_index(&self) -> usize { + self.0.largest_index() + } + + fn is_empty(&self) -> bool { + self.0.is_empty() + } + + fn by_index(&self, index: usize) -> ffi::Uuid { + self.0.by_index(index).unwrap_or_else(tc::Uuid::nil).into() + } + + fn by_uuid(&self, uuid: ffi::Uuid) -> usize { + self.0.by_uuid(uuid.into()).unwrap_or(0) + } + + fn all_uuids(&self) -> Vec { + let mut res = vec![tc::Uuid::nil().into(); self.0.largest_index() + 1]; + for (i, uuid) in self.0.iter() { + res[i] = uuid.into(); + } + res + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn uuids() { + let uuid = uuid_v4(); + assert_eq!(uuid.to_string().len(), 36); + } + + #[test] + fn operations() { + cxx::let_cxx_string!(prop = "prop"); + cxx::let_cxx_string!(prop2 = "prop2"); + cxx::let_cxx_string!(value = "value"); + cxx::let_cxx_string!(value2 = "value2"); + + let mut operations = new_operations(); + add_undo_point(&mut operations); + let mut i = 0; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(!operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(operations[i].is_undo_point()); + + let uuid = uuid_v4(); + let mut t = create_task(uuid, &mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(operations[i].is_create()); + assert!(!operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + + t.update(&prop, &value, &mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + // Note that `get_value` and `get_old_value` cannot be tested from Rust, as it is not + // possible to pass a reference to a CxxString and retain ownership of it. + assert!(operations[i].get_timestamp() > 0); + + t.update(&prop2, &value, &mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + assert!(operations[i].get_timestamp() > 0); + + t.update(&prop2, &value2, &mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + assert!(operations[i].get_timestamp() > 0); + + t.update_remove(&prop, &mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(operations[i].is_update()); + assert!(!operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + assert!(operations[i].get_timestamp() > 0); + + t.delete_task(&mut operations); + i += 1; + assert_eq!(operations.len(), i + 1); + assert!(!operations[i].is_create()); + assert!(!operations[i].is_update()); + assert!(operations[i].is_delete()); + assert!(!operations[i].is_undo_point()); + assert_eq!(operations[i].get_uuid(), uuid); + assert_eq!( + operations[i].get_old_task(), + vec![ffi::PropValuePair { + prop: "prop2".into(), + value: "value2".into(), + },] + ); + } + + #[test] + fn operation_counts() { + let mut rep = new_replica_in_memory().unwrap(); + let mut operations = new_operations(); + add_undo_point(&mut operations); + create_task(uuid_v4(), &mut operations); + create_task(uuid_v4(), &mut operations); + create_task(uuid_v4(), &mut operations); + add_undo_point(&mut operations); + rep.commit_operations(operations).unwrap(); + // Three non-undo-point operations. + assert_eq!(rep.num_local_operations().unwrap(), 3); + // Two undo points + assert_eq!(rep.num_undo_points().unwrap(), 2); + } + + #[test] + fn undo_operations() { + let mut rep = new_replica_in_memory().unwrap(); + let mut operations = new_operations(); + let (uuid1, uuid2, uuid3) = (uuid_v4(), uuid_v4(), uuid_v4()); + add_undo_point(&mut operations); + create_task(uuid1, &mut operations); + add_undo_point(&mut operations); + create_task(uuid2, &mut operations); + create_task(uuid3, &mut operations); + rep.commit_operations(operations).unwrap(); + + let undo_ops = rep.get_undo_operations().unwrap(); + assert_eq!(undo_ops.len(), 3); + assert!(undo_ops[0].is_undo_point()); + assert!(undo_ops[1].is_create()); + assert_eq!(undo_ops[1].get_uuid(), uuid2); + assert!(undo_ops[2].is_create()); + assert_eq!(undo_ops[2].get_uuid(), uuid3); + } + + #[test] + fn task_lists() { + let mut rep = new_replica_in_memory().unwrap(); + let mut operations = new_operations(); + add_undo_point(&mut operations); + create_task(uuid_v4(), &mut operations); + create_task(uuid_v4(), &mut operations); + let mut t = create_task(uuid_v4(), &mut operations); + cxx::let_cxx_string!(status = "status"); + cxx::let_cxx_string!(pending = "pending"); + t.update(&status, &pending, &mut operations); + rep.commit_operations(operations).unwrap(); + + assert_eq!(rep.all_task_data().unwrap().len(), 3); + assert_eq!(rep.pending_task_data().unwrap().len(), 1); + assert_eq!(rep.all_task_uuids().unwrap().len(), 3); + } + + #[test] + fn expire_tasks() { + let mut rep = new_replica_in_memory().unwrap(); + let mut operations = new_operations(); + add_undo_point(&mut operations); + create_task(uuid_v4(), &mut operations); + create_task(uuid_v4(), &mut operations); + create_task(uuid_v4(), &mut operations); + rep.commit_operations(operations).unwrap(); + rep.expire_tasks().unwrap(); + } + + #[test] + fn get_task_data() { + let mut rep = new_replica_in_memory().unwrap(); + + let uuid = uuid_v4(); + assert!(rep.get_task_data(uuid).unwrap().is_none()); + + let mut operations = new_operations(); + create_task(uuid, &mut operations); + rep.commit_operations(operations).unwrap(); + + let mut t = rep.get_task_data(uuid).unwrap(); + assert!(t.is_some()); + assert_eq!(t.take().get_uuid(), uuid); + } + + #[test] + fn get_task_operations() { + cxx::let_cxx_string!(prop = "prop"); + cxx::let_cxx_string!(value = "value"); + let mut rep = new_replica_in_memory().unwrap(); + + let uuid = uuid_v4(); + assert!(rep.get_task_operations(uuid).unwrap().is_empty()); + + let mut operations = new_operations(); + let mut t = create_task(uuid, &mut operations); + t.update(&prop, &value, &mut operations); + rep.commit_operations(operations).unwrap(); + + let ops = rep.get_task_operations(uuid).unwrap(); + assert_eq!(ops.len(), 2); + assert!(ops[0].is_create()); + assert!(ops[1].is_update()); + } + + #[test] + fn task_properties() { + cxx::let_cxx_string!(prop = "prop"); + cxx::let_cxx_string!(prop2 = "prop2"); + cxx::let_cxx_string!(value = "value"); + + let mut rep = new_replica_in_memory().unwrap(); + + let uuid = uuid_v4(); + let mut operations = new_operations(); + let mut t = create_task(uuid, &mut operations); + t.update(&prop, &value, &mut operations); + rep.commit_operations(operations).unwrap(); + + let t = rep.get_task_data(uuid).unwrap().take(); + assert!(t.has(&prop)); + assert!(!t.has(&prop2)); + // Note that `get` cannot be tested from Rust, as it is not possible to pass a reference to + // a CxxString and retain ownership of it. + + assert_eq!(t.properties(), vec!["prop".to_string()]); + assert_eq!( + t.items(), + vec![ffi::PropValuePair { + prop: "prop".into(), + value: "value".into(), + }] + ); + } + + #[test] + fn working_set() { + cxx::let_cxx_string!(status = "status"); + cxx::let_cxx_string!(pending = "pending"); + cxx::let_cxx_string!(completed = "completed"); + let (uuid1, uuid2, uuid3) = (uuid_v4(), uuid_v4(), uuid_v4()); + + let mut rep = new_replica_in_memory().unwrap(); + + let mut operations = new_operations(); + let mut t = create_task(uuid1, &mut operations); + t.update(&status, &pending, &mut operations); + rep.commit_operations(operations).unwrap(); + + let mut operations = new_operations(); + let mut t = create_task(uuid2, &mut operations); + t.update(&status, &pending, &mut operations); + rep.commit_operations(operations).unwrap(); + + let mut operations = new_operations(); + let mut t = create_task(uuid3, &mut operations); + t.update(&status, &completed, &mut operations); + rep.commit_operations(operations).unwrap(); + + rep.rebuild_working_set(false).unwrap(); + + let ws = rep.working_set().unwrap(); + assert!(!ws.is_empty()); + assert_eq!(ws.len(), 2); + assert_eq!(ws.largest_index(), 2); + assert_eq!(ws.by_index(1), uuid1); + assert_eq!(ws.by_uuid(uuid2), 2); + assert_eq!(ws.by_index(100), tc::Uuid::nil().into()); + assert_eq!(ws.by_uuid(uuid3), 0); + assert_eq!(ws.all_uuids(), vec![tc::Uuid::nil().into(), uuid1, uuid2]); + } +} diff --git a/src/tc/CMakeLists.txt b/src/tc/CMakeLists.txt deleted file mode 100644 index 165af059c..000000000 --- a/src/tc/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required (VERSION 3.22) - -add_subdirectory(${CMAKE_SOURCE_DIR}/src/tc/corrosion) - -# Import taskchampion-lib as a CMake library. -corrosion_import_crate( - MANIFEST_PATH "${CMAKE_SOURCE_DIR}/Cargo.toml" - LOCKED - CRATES "taskchampion-lib") - -include_directories (${CMAKE_SOURCE_DIR} - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/src/tc - ${CMAKE_SOURCE_DIR}/src/libshared/src - ${CMAKE_SOURCE_DIR}/taskchampion/lib - ${TASK_INCLUDE_DIRS}) - -set (tc_SRCS - ffi.h - util.cpp util.h - Replica.cpp Replica.h - Server.cpp Server.h - WorkingSet.cpp WorkingSet.h - Task.cpp Task.h) - -add_library (tc STATIC ${tc_SRCS}) -target_link_libraries(tc taskchampion-lib) diff --git a/src/tc/Replica.cpp b/src/tc/Replica.cpp deleted file mode 100644 index 2cc0765bd..000000000 --- a/src/tc/Replica.cpp +++ /dev/null @@ -1,291 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "tc/Replica.h" -#include "tc/Task.h" -#include "tc/Server.h" -#include "tc/WorkingSet.h" -#include "tc/util.h" -#include - -using namespace tc::ffi; - -//////////////////////////////////////////////////////////////////////////////// -tc::ReplicaGuard::ReplicaGuard (Replica &replica, Task &task) : - replica(replica), - task(task) -{ - // "steal" the reference from the Replica and store it locally, so that any - // attempt to use the Replica will fail - tcreplica = replica.inner.release(); - task.to_mut(tcreplica); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::ReplicaGuard::~ReplicaGuard () -{ - task.to_immut(); - // return the reference to the Replica. - replica.inner.reset(tcreplica); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Replica::Replica () -{ - inner = unique_tcreplica_ptr ( - tc_replica_new_in_memory (), - [](TCReplica* rep) { tc_replica_free (rep); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Replica::Replica (Replica &&other) noexcept -{ - // move inner from other - inner = unique_tcreplica_ptr ( - other.inner.release (), - [](TCReplica* rep) { tc_replica_free (rep); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Replica& tc::Replica::operator= (Replica &&other) noexcept -{ - if (this != &other) { - // move inner from other - inner = unique_tcreplica_ptr ( - other.inner.release (), - [](TCReplica* rep) { tc_replica_free (rep); }); - } - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Replica::Replica (const std::string& dir, bool create_if_missing) -{ - TCString path = tc_string_borrow (dir.c_str ()); - TCString error; - auto tcreplica = tc_replica_new_on_disk (path, create_if_missing, &error); - if (!tcreplica) { - auto errmsg = format ("Could not create replica at {1}: {2}", dir, tc_string_content (&error)); - tc_string_free (&error); - throw errmsg; - } - inner = unique_tcreplica_ptr ( - tcreplica, - [](TCReplica* rep) { tc_replica_free (rep); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::WorkingSet tc::Replica::working_set () -{ - TCWorkingSet *tcws = tc_replica_working_set (&*inner); - if (!tcws) { - throw replica_error (); - } - return WorkingSet {tcws}; -} - -//////////////////////////////////////////////////////////////////////////////// -std::optional tc::Replica::get_task (const std::string &uuid) -{ - TCTask *tctask = tc_replica_get_task (&*inner, uuid2tc (uuid)); - if (!tctask) { - auto error = tc_replica_error (&*inner); - if (error.ptr) { - throw replica_error (error); - } else { - return std::nullopt; - } - } - return std::make_optional (Task (tctask)); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Task tc::Replica::new_task (tc::Status status, const std::string &description) -{ - TCTask *tctask = tc_replica_new_task (&*inner, (tc::ffi::TCStatus)status, string2tc (description)); - if (!tctask) { - throw replica_error (); - } - return Task (tctask); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Task tc::Replica::import_task_with_uuid (const std::string &uuid) -{ - TCTask *tctask = tc_replica_import_task_with_uuid (&*inner, uuid2tc (uuid)); - if (!tctask) { - throw replica_error (); - } - return Task (tctask); -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Replica::sync (Server server, bool avoid_snapshots) -{ - // The server remains owned by this function, per tc_replica_sync docs. - auto res = tc_replica_sync (&*inner, server.inner.get(), avoid_snapshots); - if (res != TC_RESULT_OK) { - throw replica_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -TCReplicaOpList tc::Replica::get_undo_ops () -{ - return tc_replica_get_undo_ops(&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Replica::commit_undo_ops (TCReplicaOpList tc_undo_ops, int32_t *undone_out) -{ - auto res = tc_replica_commit_undo_ops (&*inner, tc_undo_ops, undone_out); - if (res != TC_RESULT_OK) { - throw replica_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Replica::free_replica_ops (TCReplicaOpList tc_undo_ops) -{ - tc_replica_op_list_free(&tc_undo_ops); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_uuid(TCReplicaOp &tc_replica_op) const -{ - TCString uuid = tc_replica_op_get_uuid(&tc_replica_op); - return tc2string(uuid); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_property(TCReplicaOp &tc_replica_op) const -{ - TCString property = tc_replica_op_get_property(&tc_replica_op); - return tc2string(property); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_value(TCReplicaOp &tc_replica_op) const -{ - TCString value = tc_replica_op_get_value(&tc_replica_op); - return tc2string(value); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_old_value(TCReplicaOp &tc_replica_op) const -{ - TCString old_value = tc_replica_op_get_old_value(&tc_replica_op); - return tc2string(old_value); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_timestamp(TCReplicaOp &tc_replica_op) const -{ - TCString timestamp = tc_replica_op_get_timestamp(&tc_replica_op); - return tc2string(timestamp); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::get_op_old_task_description(TCReplicaOp &tc_replica_op) const -{ - TCString description = tc_replica_op_get_old_task_description(&tc_replica_op); - return tc2string(description); -} - -//////////////////////////////////////////////////////////////////////////////// -int64_t tc::Replica::num_local_operations () -{ - auto num = tc_replica_num_local_operations (&*inner); - if (num < 0) { - throw replica_error (); - } - return num; -} - -//////////////////////////////////////////////////////////////////////////////// -int64_t tc::Replica::num_undo_points () -{ - auto num = tc_replica_num_undo_points (&*inner); - if (num < 0) { - throw replica_error (); - } - return num; -} - -//////////////////////////////////////////////////////////////////////////////// -std::vector tc::Replica::all_tasks () -{ - TCTaskList tasks = tc_replica_all_tasks (&*inner); - if (!tasks.items) { - throw replica_error (); - } - - std::vector all; - all.reserve (tasks.len); - for (size_t i = 0; i < tasks.len; i++) { - auto tctask = tc_task_list_take (&tasks, i); - if (tctask) { - all.push_back (Task (tctask)); - } - } - - return all; -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Replica::rebuild_working_set (bool force) -{ - auto res = tc_replica_rebuild_working_set (&*inner, force); - if (res != TC_RESULT_OK) { - throw replica_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -tc::ReplicaGuard tc::Replica::mutate_task (tc::Task &task) { - return ReplicaGuard(*this, task); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::replica_error () { - return replica_error (tc_replica_error (&*inner)); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Replica::replica_error (TCString error) { - std::string errmsg; - if (!error.ptr) { - errmsg = std::string ("Unknown TaskChampion error"); - } else { - errmsg = std::string (tc_string_content (&error)); - } - tc_string_free (&error); - return errmsg; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Replica.h b/src/tc/Replica.h deleted file mode 100644 index 6a7fcc371..000000000 --- a/src/tc/Replica.h +++ /dev/null @@ -1,126 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_TC_REPLICA -#define INCLUDED_TC_REPLICA - -#include -#include -#include -#include -#include -#include "tc/ffi.h" -#include "tc/Task.h" - -namespace tc { - class Task; - class WorkingSet; - class Server; - - // a unique_ptr to a TCReplica which will automatically free the value when - // it goes out of scope. - using unique_tcreplica_ptr = std::unique_ptr< - tc::ffi::TCReplica, - std::function>; - - // ReplicaGuard uses RAII to ensure that a Replica is not accessed while it - // is mutably borrowed (specifically, to make a task mutable). - class ReplicaGuard { - protected: - friend class Replica; - explicit ReplicaGuard (Replica &, Task &); - - public: - ~ReplicaGuard(); - - // No moving or copying allowed - ReplicaGuard (const ReplicaGuard &) = delete; - ReplicaGuard &operator=(const ReplicaGuard &) = delete; - ReplicaGuard (ReplicaGuard &&) = delete; - ReplicaGuard &operator=(Replica &&) = delete; - - private: - Replica &replica; - tc::ffi::TCReplica *tcreplica; - Task &task; - }; - - // Replica wraps the TCReplica type, managing its memory, errors, and so on. - // - // Except as noted, method names match the suffix to `tc_replica_..`. - class Replica - { - public: - Replica (); // tc_replica_new_in_memory - Replica (const std::string& dir, bool create_if_missing); // tc_replica_new_on_disk - - // This object "owns" inner, so copy is not allowed. - Replica (const Replica &) = delete; - Replica &operator=(const Replica &) = delete; - - // Explicit move constructor and assignment - Replica (Replica &&) noexcept; - Replica &operator=(Replica &&) noexcept; - - std::vector all_tasks (); -// TODO: struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); - tc::WorkingSet working_set (); - std::optional get_task (const std::string &uuid); - tc::Task new_task (Status status, const std::string &description); - tc::Task import_task_with_uuid (const std::string &uuid); -// TODO: struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); - void sync(Server server, bool avoid_snapshots); - tc::ffi::TCReplicaOpList get_undo_ops (); - void commit_undo_ops (tc::ffi::TCReplicaOpList tc_undo_ops, int32_t *undone_out); - void free_replica_ops (tc::ffi::TCReplicaOpList tc_undo_ops); - std::string get_op_uuid(tc::ffi::TCReplicaOp &tc_replica_op) const; - std::string get_op_property(tc::ffi::TCReplicaOp &tc_replica_op) const; - std::string get_op_value(tc::ffi::TCReplicaOp &tc_replica_op) const; - std::string get_op_old_value(tc::ffi::TCReplicaOp &tc_replica_op) const; - std::string get_op_timestamp(tc::ffi::TCReplicaOp &tc_replica_op) const; - std::string get_op_old_task_description(tc::ffi::TCReplicaOp &tc_replica_op) const; - int64_t num_local_operations (); - int64_t num_undo_points (); -// TODO: TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); - void rebuild_working_set (bool force); - - ReplicaGuard mutate_task(tc::Task &); - void immut_task(tc::Task &); - - protected: - friend class ReplicaGuard; - unique_tcreplica_ptr inner; - - // construct an error message from tc_replica_error, or from the given - // string retrieved from tc_replica_error. - std::string replica_error (); - std::string replica_error (tc::ffi::TCString string); - }; -} - - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Server.cpp b/src/tc/Server.cpp deleted file mode 100644 index 9d595e901..000000000 --- a/src/tc/Server.cpp +++ /dev/null @@ -1,122 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "tc/Server.h" -#include "tc/util.h" - -using namespace tc::ffi; - -//////////////////////////////////////////////////////////////////////////////// -tc::Server -tc::Server::new_local (const std::string &server_dir) -{ - TCString tc_server_dir = tc_string_borrow (server_dir.c_str ()); - TCString error; - auto tcserver = tc_server_new_local (tc_server_dir, &error); - if (!tcserver) { - std::string errmsg = format ("Could not configure local server at {1}: {2}", - server_dir, tc_string_content (&error)); - tc_string_free (&error); - throw errmsg; - } - return Server (unique_tcserver_ptr ( - tcserver, - [](TCServer* rep) { tc_server_free (rep); })); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Server -tc::Server::new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret) -{ - TCString tc_origin = tc_string_borrow (origin.c_str ()); - TCString tc_client_id = tc_string_borrow (client_id.c_str ()); - TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ()); - - TCUuid tc_client_uuid; - if (tc_uuid_from_str(tc_client_id, &tc_client_uuid) != TC_RESULT_OK) { - tc_string_free(&tc_origin); - tc_string_free(&tc_encryption_secret); - throw format ("client_id '{1}' is not a valid UUID", client_id); - } - - TCString error; - auto tcserver = tc_server_new_sync (tc_origin, tc_client_uuid, tc_encryption_secret, &error); - if (!tcserver) { - std::string errmsg = format ("Could not configure connection to server at {1}: {2}", - origin, tc_string_content (&error)); - tc_string_free (&error); - throw errmsg; - } - return Server (unique_tcserver_ptr ( - tcserver, - [](TCServer* rep) { tc_server_free (rep); })); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Server -tc::Server::new_gcp (const std::string &bucket, const std::string &credential_path, const std::string &encryption_secret) -{ - TCString tc_bucket = tc_string_borrow (bucket.c_str ()); - TCString tc_encryption_secret = tc_string_borrow (encryption_secret.c_str ()); - TCString tc_credential_path = tc_string_borrow (credential_path.c_str ()); - - TCString error; - auto tcserver = tc_server_new_gcp (tc_bucket, tc_credential_path, tc_encryption_secret, &error); - if (!tcserver) { - std::string errmsg = format ("Could not configure connection to GCP bucket {1}: {2}", - bucket, tc_string_content (&error)); - tc_string_free (&error); - throw errmsg; - } - return Server (unique_tcserver_ptr ( - tcserver, - [](TCServer* rep) { tc_server_free (rep); })); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Server::Server (tc::Server &&other) noexcept -{ - // move inner from other - inner = unique_tcserver_ptr ( - other.inner.release (), - [](TCServer* rep) { tc_server_free (rep); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Server& tc::Server::operator= (tc::Server &&other) noexcept -{ - if (this != &other) { - // move inner from other - inner = unique_tcserver_ptr ( - other.inner.release (), - [](TCServer* rep) { tc_server_free (rep); }); - } - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Server.h b/src/tc/Server.h deleted file mode 100644 index 08700c4bc..000000000 --- a/src/tc/Server.h +++ /dev/null @@ -1,85 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_TC_SERVER -#define INCLUDED_TC_SERVER - -#include -#include -#include -#include -#include -#include "tc/ffi.h" - -namespace tc { - // a unique_ptr to a TCServer which will automatically free the value when - // it goes out of scope. - using unique_tcserver_ptr = std::unique_ptr< - tc::ffi::TCServer, - std::function>; - - // Server wraps the TCServer type, managing its memory, errors, and so on. - // - // Except as noted, method names match the suffix to `tc_server_..`. - class Server - { - public: - // Construct a null server - Server () = default; - - // Construct a local server (tc_server_new_local). - static Server new_local (const std::string& server_dir); - - // Construct a remote server (tc_server_new_sync). - static Server new_sync (const std::string &origin, const std::string &client_id, const std::string &encryption_secret); - - // Construct a GCP server (tc_server_new_gcp). - static Server new_gcp (const std::string &bucket, const std::string &credential_path, const std::string &encryption_secret); - - // This object "owns" inner, so copy is not allowed. - Server (const Server &) = delete; - Server &operator=(const Server &) = delete; - - // Explicit move constructor and assignment - Server (Server &&) noexcept; - Server &operator=(Server &&) noexcept; - - protected: - Server (unique_tcserver_ptr inner) : inner(std::move(inner)) {}; - - unique_tcserver_ptr inner; - - // Replica accesses the inner pointer to call tc_replica_sync - friend class Replica; - - // construct an error message from the given string. - std::string server_error (tc::ffi::TCString string); - }; -} - - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Task.cpp b/src/tc/Task.cpp deleted file mode 100644 index 69a354a9f..000000000 --- a/src/tc/Task.cpp +++ /dev/null @@ -1,194 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "tc/Task.h" -#include "tc/util.h" - -using namespace tc::ffi; - -//////////////////////////////////////////////////////////////////////////////// -tc::Task::Task (TCTask *tctask) -{ - inner = unique_tctask_ptr( - tctask, - [](TCTask* task) { tc_task_free(task); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Task::Task (Task &&other) noexcept -{ - // move inner from other - inner = unique_tctask_ptr( - other.inner.release(), - [](TCTask* task) { tc_task_free(task); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Task& tc::Task::operator= (Task &&other) noexcept -{ - if (this != &other) { - // move inner from other - inner = unique_tctask_ptr( - other.inner.release(), - [](TCTask* task) { tc_task_free(task); }); - } - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Task::to_mut (TCReplica *replica) -{ - tc_task_to_mut(&*inner, replica); -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Task::to_immut () -{ - tc_task_to_immut(&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Task::get_uuid () const -{ - auto uuid = tc_task_get_uuid(&*inner); - return tc2uuid(uuid); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::Status tc::Task::get_status () const -{ - auto status = tc_task_get_status(&*inner); - return tc::Status(status); -} - -//////////////////////////////////////////////////////////////////////////////// -std::map tc::Task::get_taskmap () const -{ - TCKVList kv = tc_task_get_taskmap (&*inner); - if (!kv.items) { - throw task_error (); - } - - std::map taskmap; - for (size_t i = 0; i < kv.len; i++) { - auto k = tc2string_clone(kv.items[i].key); - auto v = tc2string_clone(kv.items[i].value); - taskmap[k] = v; - } - - return taskmap; -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Task::get_description () const -{ - auto desc = tc_task_get_description(&*inner); - return tc2string(desc); -} - -//////////////////////////////////////////////////////////////////////////////// -std::optional tc::Task::get_value (std::string property) const -{ - auto maybe_desc = tc_task_get_value (&*inner, string2tc(property)); - if (maybe_desc.ptr == NULL) { - return std::nullopt; - } - return std::make_optional(tc2string(maybe_desc)); -} - -bool tc::Task::is_waiting () const -{ - return tc_task_is_waiting (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -bool tc::Task::is_active () const -{ - return tc_task_is_active (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -bool tc::Task::is_blocked () const -{ - return tc_task_is_blocked (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -bool tc::Task::is_blocking () const -{ - return tc_task_is_blocking (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Task::set_status (tc::Status status) -{ - TCResult res = tc_task_set_status (&*inner, (TCStatus)status); - if (res != TC_RESULT_OK) { - throw task_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Task::set_value (std::string property, std::optional value) -{ - TCResult res; - if (value.has_value()) { - res = tc_task_set_value (&*inner, string2tc(property), string2tc(value.value())); - } else { - TCString nullstr; - nullstr.ptr = NULL; - res = tc_task_set_value (&*inner, string2tc(property), nullstr); - } - if (res != TC_RESULT_OK) { - throw task_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void tc::Task::set_modified (time_t modified) -{ - TCResult res = tc_task_set_modified (&*inner, modified); - if (res != TC_RESULT_OK) { - throw task_error (); - } -} - -//////////////////////////////////////////////////////////////////////////////// -std::string tc::Task::task_error () const { - TCString error = tc_task_error (&*inner); - std::string errmsg; - if (!error.ptr) { - errmsg = std::string ("Unknown TaskChampion error"); - } else { - errmsg = std::string (tc_string_content (&error)); - } - tc_string_free (&error); - return errmsg; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/Task.h b/src/tc/Task.h deleted file mode 100644 index 2710768d3..000000000 --- a/src/tc/Task.h +++ /dev/null @@ -1,130 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_TC_TASK -#define INCLUDED_TC_TASK - -#include -#include -#include -#include -#include -#include "tc/ffi.h" - -namespace tc { - class Replica; - class ReplicaGuard; - - enum Status { - Pending = tc::ffi::TC_STATUS_PENDING, - Completed = tc::ffi::TC_STATUS_COMPLETED, - Deleted = tc::ffi::TC_STATUS_DELETED, - Recurring = tc::ffi::TC_STATUS_RECURRING, - Unknown = tc::ffi::TC_STATUS_UNKNOWN, - }; - - // a unique_ptr to a TCReplica which will automatically free the value when - // it goes out of scope. - using unique_tctask_ptr = std::unique_ptr< - tc::ffi::TCTask, - std::function>; - - - // Task wraps the TCTask type, managing its memory, errors, and so on. - // - // Except as noted, method names match the suffix to `tc_task_..`. - class Task - { - protected: - // Tasks may only be created and made mutable/immutable - // by tc::Replica - friend class tc::Replica; - explicit Task (tc::ffi::TCTask *); - - // RplicaGuard handles mut/immut - friend class tc::ReplicaGuard; - void to_mut(tc::ffi::TCReplica *); - void to_immut(); - - public: - // This object "owns" inner, so copy is not allowed. - Task (const Task &) = delete; - Task &operator=(const Task &) = delete; - - // Explicit move constructor and assignment - Task (Task &&) noexcept; - Task &operator=(Task &&) noexcept; - - std::string get_uuid () const; - Status get_status () const; - std::map get_taskmap() const; - std::string get_description() const; - std::optional get_value(std::string property) const; -// TODO: time_t tc_task_get_entry(struct TCTask *task); -// TODO: time_t tc_task_get_wait(struct TCTask *task); -// TODO: time_t tc_task_get_modified(struct TCTask *task); - bool is_waiting() const; - bool is_active() const; - bool is_blocked() const; - bool is_blocking() const; -// TODO: bool tc_task_has_tag(struct TCTask *task, struct TCString tag); -// TODO: struct TCStringList tc_task_get_tags(struct TCTask *task); -// TODO: struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); -// TODO: struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); -// TODO: struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); -// TODO: struct TCUdaList tc_task_get_udas(struct TCTask *task); -// TODO: struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); - void set_status(Status status); -// TODO: TCResult tc_task_set_description(struct TCTask *task, struct TCString description); - void set_value(std::string property, std::optional value); -// TODO: TCResult tc_task_set_entry(struct TCTask *task, time_t entry); -// TODO: TCResult tc_task_set_wait(struct TCTask *task, time_t wait); - void set_modified(time_t modified); -// TODO: TCResult tc_task_start(struct TCTask *task); -// TODO: TCResult tc_task_stop(struct TCTask *task); -// TODO: TCResult tc_task_done(struct TCTask *task); -// TODO: TCResult tc_task_delete(struct TCTask *task); -// TODO: TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); -// TODO: TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); -// TODO: TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); -// TODO: TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); -// TODO: TCResult tc_task_set_uda(struct TCTask *task, -// TODO: TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); -// TODO: TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); -// TODO: TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); - - private: - unique_tctask_ptr inner; - - std::string task_error () const; // tc_task_error - }; -} - -// TODO: struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); -// TODO: void tc_task_list_free(struct TCTaskList *tasks); - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/WorkingSet.cpp b/src/tc/WorkingSet.cpp deleted file mode 100644 index 4f116aaa5..000000000 --- a/src/tc/WorkingSet.cpp +++ /dev/null @@ -1,98 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include "tc/WorkingSet.h" -#include "tc/Task.h" -#include "tc/util.h" - -using namespace tc::ffi; - -//////////////////////////////////////////////////////////////////////////////// -tc::WorkingSet::WorkingSet (WorkingSet &&other) noexcept -{ - // move inner from other - inner = unique_tcws_ptr ( - other.inner.release (), - [](TCWorkingSet* ws) { tc_working_set_free (ws); }); -} - -//////////////////////////////////////////////////////////////////////////////// -tc::WorkingSet& tc::WorkingSet::operator= (WorkingSet &&other) noexcept -{ - if (this != &other) { - // move inner from other - inner = unique_tcws_ptr ( - other.inner.release (), - [](TCWorkingSet* ws) { tc_working_set_free (ws); }); - } - return *this; -} - -//////////////////////////////////////////////////////////////////////////////// -tc::WorkingSet::WorkingSet (tc::ffi::TCWorkingSet* tcws) -{ - inner = unique_tcws_ptr ( - tcws, - [](TCWorkingSet* ws) { tc_working_set_free (ws); }); -} - -//////////////////////////////////////////////////////////////////////////////// -size_t tc::WorkingSet::len () const noexcept -{ - return tc_working_set_len (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -size_t tc::WorkingSet::largest_index () const noexcept -{ - return tc_working_set_largest_index (&*inner); -} - -//////////////////////////////////////////////////////////////////////////////// -std::optional tc::WorkingSet::by_index (size_t index) const noexcept -{ - TCUuid uuid; - if (tc_working_set_by_index (&*inner, index, &uuid)) { - return std::make_optional (tc2uuid (uuid)); - } else { - return std::nullopt; - } -} - -//////////////////////////////////////////////////////////////////////////////// -std::optional tc::WorkingSet::by_uuid (const std::string &uuid) const noexcept -{ - auto index = tc_working_set_by_uuid (&*inner, uuid2tc (uuid)); - if (index > 0) { - return std::make_optional (index); - } else { - return std::nullopt; - } -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/WorkingSet.h b/src/tc/WorkingSet.h deleted file mode 100644 index 7b6c63423..000000000 --- a/src/tc/WorkingSet.h +++ /dev/null @@ -1,75 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#ifndef INCLUDED_TC_WORKINGSET -#define INCLUDED_TC_WORKINGSET - -#include -#include -#include -#include -#include "tc/ffi.h" -#include "tc/Task.h" - -namespace tc { - class Task; - - // a unique_ptr to a TCWorkingSet which will automatically free the value when - // it goes out of scope. - using unique_tcws_ptr = std::unique_ptr< - tc::ffi::TCWorkingSet, - std::function>; - - // WorkingSet wraps the TCWorkingSet type, managing its memory, errors, and so on. - // - // Except as noted, method names match the suffix to `tc_working_set_..`. - class WorkingSet - { - protected: - friend class tc::Replica; - WorkingSet (tc::ffi::TCWorkingSet*); // via tc_replica_working_set - - public: - // This object "owns" inner, so copy is not allowed. - WorkingSet (const WorkingSet &) = delete; - WorkingSet &operator=(const WorkingSet &) = delete; - - // Explicit move constructor and assignment - WorkingSet (WorkingSet &&) noexcept; - WorkingSet &operator=(WorkingSet &&) noexcept; - - size_t len () const noexcept; // tc_working_set_len - size_t largest_index () const noexcept; // tc_working_set_largest_index - std::optional by_index (size_t index) const noexcept; // tc_working_set_by_index - std::optional by_uuid (const std::string &index) const noexcept; // tc_working_set_by_uuid - - private: - unique_tcws_ptr inner; - }; -} - -#endif -//////////////////////////////////////////////////////////////////////////////// diff --git a/src/tc/corrosion b/src/tc/corrosion deleted file mode 160000 index 8ddd6d56c..000000000 --- a/src/tc/corrosion +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8ddd6d56ca597cb855f532e9ba4c7bc1cbe0803b diff --git a/src/tw b/src/tw deleted file mode 120000 index c077ac0c7..000000000 --- a/src/tw +++ /dev/null @@ -1 +0,0 @@ -task \ No newline at end of file diff --git a/src/util.cpp b/src/util.cpp index 02a97c44c..6b945f959 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -25,6 +25,8 @@ //////////////////////////////////////////////////////////////////////////////// #include +// cmake.h include header must come first + #include #include // If is included, put it after , because it includes @@ -32,48 +34,39 @@ #ifdef FREEBSD #define _WITH_GETLINE #endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include #include #include +#include #include -#include +#include +#include +#include #include +#include #include #include -#include -#define STRING_UTIL_CONFIRM_YES "yes" -#define STRING_UTIL_CONFIRM_YES_U "Yes" -#define STRING_UTIL_CONFIRM_NO "no" -#define STRING_UTIL_CONFIRM_ALL "all" -#define STRING_UTIL_CONFIRM_ALL_U "All" -#define STRING_UTIL_CONFIRM_QUIT "quit" +#include +#include +#include + +#define STRING_UTIL_CONFIRM_YES "yes" +#define STRING_UTIL_CONFIRM_YES_U "Yes" +#define STRING_UTIL_CONFIRM_NO "no" +#define STRING_UTIL_CONFIRM_ALL "all" +#define STRING_UTIL_CONFIRM_ALL_U "All" +#define STRING_UTIL_CONFIRM_QUIT "quit" static const char* newline = "\n"; -static const char* noline = ""; +static const char* noline = ""; -//////////////////////////////////////////////////////////////////////////////// -static void signal_handler (int s) -{ - if (s == SIGINT) - { +//////////////////////////////////////////////////////////////////////////////// +static void signal_handler(int s) { + if (s == SIGINT) { std::cout << "\n\nInterrupted: No changes made.\n"; - exit (1); + exit(1); } } @@ -82,45 +75,38 @@ static void signal_handler (int s) // 1 = yes // 2 = all // 3 = quit -int confirm4 (const std::string& question) -{ - std::vector options {STRING_UTIL_CONFIRM_YES_U, - STRING_UTIL_CONFIRM_YES, - STRING_UTIL_CONFIRM_NO, - STRING_UTIL_CONFIRM_ALL_U, - STRING_UTIL_CONFIRM_ALL, - STRING_UTIL_CONFIRM_QUIT}; - std::vector matches; +int confirm4(const std::string& question) { + std::vector options{STRING_UTIL_CONFIRM_YES_U, STRING_UTIL_CONFIRM_YES, + STRING_UTIL_CONFIRM_NO, STRING_UTIL_CONFIRM_ALL_U, + STRING_UTIL_CONFIRM_ALL, STRING_UTIL_CONFIRM_QUIT}; + std::vector matches; - signal (SIGINT, signal_handler); + signal(SIGINT, signal_handler); - do - { - std::cout << question - << " (" - << options[1] << '/' - << options[2] << '/' - << options[4] << '/' - << options[5] - << ") "; + do { + std::cout << question << " (" << options[1] << '/' << options[2] << '/' << options[4] << '/' + << options[5] << ") "; - std::string answer {""}; - std::getline (std::cin, answer); - Context::getContext ().debug ("STDIN '" + answer + '\''); - answer = std::cin.eof () ? STRING_UTIL_CONFIRM_QUIT : Lexer::lowerCase (Lexer::trim (answer)); - autoComplete (answer, options, matches, 1); // Hard-coded 1. - } - while (! std::cin.eof () && matches.size () != 1); + std::string answer{""}; + std::getline(std::cin, answer); + Context::getContext().debug("STDIN '" + answer + '\''); + answer = std::cin.eof() ? STRING_UTIL_CONFIRM_QUIT : Lexer::lowerCase(Lexer::trim(answer)); + autoComplete(answer, options, matches, 1); // Hard-coded 1. + } while (!std::cin.eof() && matches.size() != 1); - signal (SIGINT, SIG_DFL); + signal(SIGINT, SIG_DFL); - if (matches.size () == 1) - { - if (matches[0] == STRING_UTIL_CONFIRM_YES_U) return 1; - else if (matches[0] == STRING_UTIL_CONFIRM_YES) return 1; - else if (matches[0] == STRING_UTIL_CONFIRM_ALL_U) return 2; - else if (matches[0] == STRING_UTIL_CONFIRM_ALL) return 2; - else if (matches[0] == STRING_UTIL_CONFIRM_QUIT) return 3; + if (matches.size() == 1) { + if (matches[0] == STRING_UTIL_CONFIRM_YES_U) + return 1; + else if (matches[0] == STRING_UTIL_CONFIRM_YES) + return 1; + else if (matches[0] == STRING_UTIL_CONFIRM_ALL_U) + return 2; + else if (matches[0] == STRING_UTIL_CONFIRM_ALL) + return 2; + else if (matches[0] == STRING_UTIL_CONFIRM_QUIT) + return 3; } return 0; @@ -133,16 +119,15 @@ int confirm4 (const std::string& question) // For the implementation details, refer to // https://svnweb.freebsd.org/base/head/sys/kern/kern_uuid.c #if defined(FREEBSD) || defined(OPENBSD) -const std::string uuid () -{ +const std::string uuid() { uuid_t id; uint32_t status; - char *buffer (0); - uuid_create (&id, &status); - uuid_to_string (&id, &buffer, &status); + char* buffer(0); + uuid_create(&id, &status); + uuid_to_string(&id, &buffer, &status); - std::string res (buffer); - free (buffer); + std::string res(buffer); + free(buffer); return res; } @@ -151,26 +136,24 @@ const std::string uuid () //////////////////////////////////////////////////////////////////////////////// #ifndef HAVE_UUID_UNPARSE_LOWER // Older versions of libuuid don't have uuid_unparse_lower(), only uuid_unparse() -void uuid_unparse_lower (uuid_t uu, char *out) -{ - uuid_unparse (uu, out); - // Characters in out are either 0-9, a-z, '-', or A-Z. A-Z is mapped to - // a-z by bitwise or with 0x20, and the others already have this bit set - for (size_t i = 0; i < 36; ++i) out[i] |= 0x20; +void uuid_unparse_lower(uuid_t uu, char* out) { + uuid_unparse(uu, out); + // Characters in out are either 0-9, a-z, '-', or A-Z. A-Z is mapped to + // a-z by bitwise or with 0x20, and the others already have this bit set + for (size_t i = 0; i < 36; ++i) out[i] |= 0x20; } #endif -const std::string uuid () -{ +const std::string uuid() { uuid_t id; - uuid_generate (id); - char buffer[100] {}; - uuid_unparse_lower (id, buffer); + uuid_generate(id); + char buffer[100]{}; + uuid_unparse_lower(id, buffer); // Bug found by Steven de Brouwer. buffer[36] = '\0'; - return std::string (buffer); + return std::string(buffer); } #endif @@ -199,19 +182,15 @@ const std::string uuid () // - delimiter is the character used to split up projects into subprojects. // - defaults to the period, '.' // -const std::string indentProject ( - const std::string& project, - const std::string& whitespace /* = " " */, - char delimiter /* = '.' */) -{ +const std::string indentProject(const std::string& project, + const std::string& whitespace /* = " " */, + char delimiter /* = '.' */) { // Count the delimiters in *i. std::string prefix = ""; std::string::size_type pos = 0; std::string::size_type lastpos = 0; - while ((pos = project.find (delimiter, pos + 1)) != std::string::npos) - { - if (pos != project.size () - 1) - { + while ((pos = project.find(delimiter, pos + 1)) != std::string::npos) { + if (pos != project.size() - 1) { prefix += whitespace; lastpos = pos; } @@ -221,99 +200,65 @@ const std::string indentProject ( if (lastpos == 0) child = project; else - child = project.substr (lastpos + 1); + child = project.substr(lastpos + 1); return prefix + child; } //////////////////////////////////////////////////////////////////////////////// -const std::vector extractParents ( - const std::string& project, - const char& delimiter /* = '.' */) -{ - std::vector vec; +const std::vector extractParents(const std::string& project, + const char& delimiter /* = '.' */) { + std::vector vec; std::string::size_type pos = 0; std::string::size_type copyUntil = 0; - while ((copyUntil = project.find (delimiter, pos + 1)) != std::string::npos) - { - if (copyUntil != project.size () - 1) - vec.push_back (project.substr (0, copyUntil)); + while ((copyUntil = project.find(delimiter, pos + 1)) != std::string::npos) { + if (copyUntil != project.size() - 1) vec.push_back(project.substr(0, copyUntil)); pos = copyUntil; } return vec; } //////////////////////////////////////////////////////////////////////////////// -#ifndef HAVE_TIMEGM -time_t timegm (struct tm *tm) -{ - time_t ret; - char *tz; - tz = getenv ("TZ"); - setenv ("TZ", "UTC", 1); - tzset (); - ret = mktime (tm); - if (tz) - setenv ("TZ", tz, 1); - else - unsetenv ("TZ"); - tzset (); - return ret; -} -#endif - -//////////////////////////////////////////////////////////////////////////////// -bool nontrivial (const std::string& input) -{ +bool nontrivial(const std::string& input) { std::string::size_type i = 0; int character; - while ((character = utf8_next_char (input, i))) - if (! unicodeWhitespace (character)) - return true; + while ((character = utf8_next_char(input, i))) + if (!unicodeWhitespace(character)) return true; return false; } //////////////////////////////////////////////////////////////////////////////// -const char* optionalBlankLine () -{ - return Context::getContext ().verbose ("blank") ? newline : noline; +const char* optionalBlankLine() { + return Context::getContext().verbose("blank") ? newline : noline; } //////////////////////////////////////////////////////////////////////////////// -void setHeaderUnderline (Table& table) -{ +void setHeaderUnderline(Table& table) { // If an alternating row color is specified, notify the table. - if (Context::getContext ().color ()) - { - Color alternate (Context::getContext ().config.get ("color.alternate")); - table.colorOdd (alternate); - table.intraColorOdd (alternate); + if (Context::getContext().color()) { + Color alternate(Context::getContext().config.get("color.alternate")); + table.colorOdd(alternate); + table.intraColorOdd(alternate); - if (Context::getContext ().config.getBoolean ("fontunderline")) - { - table.colorHeader (Color ("underline " + Context::getContext ().config.get ("color.label"))); + if (Context::getContext().config.getBoolean("fontunderline")) { + table.colorHeader(Color("underline " + Context::getContext().config.get("color.label"))); + } else { + table.colorHeader(Color(Context::getContext().config.get("color.label"))); + table.underlineHeaders(); } + } else { + if (Context::getContext().config.getBoolean("fontunderline")) + table.colorHeader(Color("underline")); else - { - table.colorHeader (Color (Context::getContext ().config.get ("color.label"))); - table.underlineHeaders (); - } - } - else - { - if (Context::getContext ().config.getBoolean ("fontunderline")) - table.colorHeader (Color ("underline")); - else - table.underlineHeaders (); + table.underlineHeaders(); } } //////////////////////////////////////////////////////////////////////////////// // Perform strtol on a string and check if the extracted value matches. // -bool extractLongInteger (const std::string& input, long& output) -{ - output = strtol (input.c_str (), nullptr, 10); - return (format ("{1}", output) == input); +bool extractLongInteger(const std::string& input, long& output) { + output = strtol(input.c_str(), nullptr, 10); + return (format("{1}", output) == input); } diff --git a/src/util.h b/src/util.h index f900e989e..d2be723f5 100644 --- a/src/util.h +++ b/src/util.h @@ -28,10 +28,12 @@ #define INCLUDED_UTIL #include +// cmake.h include header must come first + +#include + #include #include -#include -#include #if defined(FREEBSD) || defined(OPENBSD) #include #else @@ -40,30 +42,22 @@ #include // util.cpp -int confirm4 (const std::string&); +int confirm4(const std::string&); #ifndef HAVE_UUID_UNPARSE_LOWER -void uuid_unparse_lower (uuid_t uu, char *out); +void uuid_unparse_lower(uuid_t uu, char* out); #endif -const std::string uuid (); +const std::string uuid(); -const std::string indentProject ( - const std::string&, - const std::string& whitespace = " ", - char delimiter = '.'); +const std::string indentProject(const std::string&, const std::string& whitespace = " ", + char delimiter = '.'); -const std::vector extractParents ( - const std::string&, - const char& delimiter = '.'); +const std::vector extractParents(const std::string&, const char& delimiter = '.'); -#ifndef HAVE_TIMEGM - time_t timegm (struct tm *tm); -#endif - -bool nontrivial (const std::string&); -const char* optionalBlankLine (); -void setHeaderUnderline (Table&); -bool extractLongInteger (const std::string&, long&); +bool nontrivial(const std::string&); +const char* optionalBlankLine(); +void setHeaderUnderline(Table&); +bool extractLongInteger(const std::string&, long&); #endif //////////////////////////////////////////////////////////////////////////////// diff --git a/taskchampion/.changelogs/.gitignore b/taskchampion/.changelogs/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/taskchampion/.changelogs/2021-10-03-server-storage.md b/taskchampion/.changelogs/2021-10-03-server-storage.md deleted file mode 100644 index 7834601d5..000000000 --- a/taskchampion/.changelogs/2021-10-03-server-storage.md +++ /dev/null @@ -1,2 +0,0 @@ -- The SQLite server storage schema has changed incompatibly, in order to add support for snapshots. - As this is not currently ready for production usage, no migration path is provided except deleting the existing database. diff --git a/taskchampion/.changelogs/2021-10-11-issue23-client.md b/taskchampion/.changelogs/2021-10-11-issue23-client.md deleted file mode 100644 index 91a6b0f9e..000000000 --- a/taskchampion/.changelogs/2021-10-11-issue23-client.md +++ /dev/null @@ -1,2 +0,0 @@ -- The `avoid_snapshots` configuration value, if set, will cause the replica to - avoid creating snapshots unless required. diff --git a/taskchampion/.changelogs/2021-10-16-issue299.md b/taskchampion/.changelogs/2021-10-16-issue299.md deleted file mode 100644 index a74af24c5..000000000 --- a/taskchampion/.changelogs/2021-10-16-issue299.md +++ /dev/null @@ -1 +0,0 @@ -- The encryption format used for synchronization has changed incompatibly diff --git a/taskchampion/.changelogs/2021-10-25-issue23-integration.md b/taskchampion/.changelogs/2021-10-25-issue23-integration.md deleted file mode 100644 index d10a4d0ec..000000000 --- a/taskchampion/.changelogs/2021-10-25-issue23-integration.md +++ /dev/null @@ -1 +0,0 @@ -- The details of how task start/stop is represented have changed. Any existing tasks will all be treated as inactive (stopped). diff --git a/taskchampion/.gitignore b/taskchampion/.gitignore deleted file mode 100644 index 72429aeef..000000000 --- a/taskchampion/.gitignore +++ /dev/null @@ -1 +0,0 @@ -**/*.rs.bk diff --git a/taskchampion/CHANGELOG.md b/taskchampion/CHANGELOG.md deleted file mode 100644 index 91a692b5a..000000000 --- a/taskchampion/CHANGELOG.md +++ /dev/null @@ -1,26 +0,0 @@ -# Changelog - -## [Unreleased] - -Note: unreleased change log entries are kept in `.changelogs/` directory in repo root, and can be added with `./script/changelog.py add "Added thing for reason" - -## 0.4.1 - 2021-09-24 -- Fix for the build process to include the serde feature "derive". 0.4.0 could not be published to crates.io due to this bug. - -## 0.4.0 - 2021-09-25 -- Breaking: Removed the KV based storage backend in client and server, and replaced with SQLite ([Issue #131](https://github.com/taskchampion/taskchampion/issues/131), [PR #206](https://github.com/taskchampion/taskchampion/pull/206)) - -## 0.3.0 - 2021-01-11 -- Flexible named reports -- Updates to the TaskChampion crate API -- Usability improvements - -## 0.2.0 - 2020-11-30 - -This release is the first "MVP" version of this tool. It can do basic task operations, and supports a synchronization. Major missing features are captured in issues, but briefly: - - better command-line API, similar to TaskWarrior - authentication of the replica / server protocol - encryption of replica data before transmission to the server - lots of task features (tags, annotations, dependencies, ..) - lots of CLI features (filtering, modifying, ..) diff --git a/taskchampion/CODE_OF_CONDUCT.md b/taskchampion/CODE_OF_CONDUCT.md deleted file mode 100644 index 807def586..000000000 --- a/taskchampion/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported by contacting the project team at dustin@cs.uchicago.edu. All -complaints will be reviewed and investigated and will result in a response that -is deemed necessary and appropriate to the circumstances. The project team is -obligated to maintain confidentiality with regard to the reporter of an incident. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/taskchampion/CONTRIBUTING.md b/taskchampion/CONTRIBUTING.md deleted file mode 100644 index 7b44aad75..000000000 --- a/taskchampion/CONTRIBUTING.md +++ /dev/null @@ -1,62 +0,0 @@ -# Welcome - -This application is still in a pre-release state. -That means it's very open to contributions, and we'd love to have your help! - -It also means that things are changing quickly, and lots of stuff is planned that's not quite done yet. -If you would like to work on TaskChampion, please contact the developers (via the issue tracker) before spending a lot of time working on a pull request. -Doing so may save you some wasted time and frustration! - -A good starting point might be one of the issues tagged with ["good first issue"][first]. - -[first]: https://github.com/taskchampion/taskchampion/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22 - -# Other Ways To Help - -The best way to help this project to grow is to help spread awareness of it. -Tell your friends, post to social media, blog about it -- whatever works best! - -Other ideas; - * Improve the documentation where it's unclear or lacking some information - * Build and maintain tools that integrate with TaskChampion - -# Development Guide - -TaskChampion is a typical Rust application. -To work on TaskChampion, you'll need to [install the latest version of Rust](https://www.rust-lang.org/tools/install). - -## Running Tests - -It's always a good idea to make sure tests run before you start hacking on a project. -Run `cargo test` from the top-level of this repository to run the tests. - -## Read the Source - -Aside from that, start reading the docs and the source to learn more! -The book documentation explains lots of the concepts in the design of TaskChampion. -It is linked from the README. - -There are three important crates in this repository. -You may be able to limit the scope of what you need to understand to just one crate. - * `taskchampion` is the core functionality of the application, implemented as a library - * `taskchampion-lib` implements a C API for `taskchampion`, used by Taskwarrior - * `integration-tests` contains some tests for integrations between multiple crates. - -You can generate the documentation for the `taskchampion` crate with `cargo doc --release --open -p taskchampion`. - -## Making a Pull Request - -We expect contributors to follow the [GitHub Flow](https://guides.github.com/introduction/flow/). -Aside from that, we have no particular requirements on pull requests. -Make your patch, double-check that it's complete (tests? docs? documentation comments?), and make a new pull request. - -Any non-trivial change (particularly those that change the behaviour of the application, or change the API) should be noted in the projects changelog. -In order to manage this, changelog entries are stored as text files in the `.changelog/` directory at the repository root. - -To add a new changelog entry, you can simply run `python3 ./script/changelog.py add "Fixed thingo to increase zorbloxification [Issue #2](http://example.com)` - -This creates a file named `./changelogs/yyyy-mm-dd-branchname.md` (timestamp, current git branch) which contains a markdown snippet. - -If you don't have a Python 3 intepreter installed, you can simply create this file manually. It should contain a list item like `- Fixed thingo [...]` - -Periodically (probably just before release), these changelog entries are concatenated combined together and added into the `CHANGELOG.md` file. diff --git a/taskchampion/LICENSE b/taskchampion/LICENSE deleted file mode 100644 index f0c9756e1..000000000 --- a/taskchampion/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 Dustin J. Mitchell - -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/taskchampion/POLICY.md b/taskchampion/POLICY.md deleted file mode 100644 index 3d84cbb82..000000000 --- a/taskchampion/POLICY.md +++ /dev/null @@ -1,45 +0,0 @@ -# Compatibility & deprecation - -Until TaskChampion reaches [v1.0.0](https://github.com/taskchampion/taskchampion/milestone/7), nothing is set in stone. That being said, we aim for the following: - -1. Major versions represent significant change and may be incompatible with previous major release. -2. Minor versions are always backwards compatible and might add some new functionality. -3. Patch versions should not introduce any new functionality and do what name implies — fix bugs. - -As there are no major releases yet, we do not support any older versions. Users are encouraged to use the latest release. - -## ABI policy - -1. We target stable `rustc`. -2. TaskChampion will never upgrade any storage to a non-compatible version without explicit user's request. - -## API policy - -1. Deprecated features return a warning at least 1 minor version prior to being removed. - - Example: - - > If support of `--bar` is to be dropped in v2.0.0, we shall announce it in v1.9.0 at latest. - -2. We aim to issue a notice of newly added functionality when appropriate. - - Example: - - > "NOTICE: Since v1.1.0 you can use `--foo` in conjunction with `--bar`. Foobar!" - -3. TaskChampion always uses UTF-8. - -## Command-line interface - -Considered to be part of the API policy. - -## CLI exit codes - -- `0` - No errors, normal exit. -- `1` - Generic error. -- `2` - Never used to avoid conflicts with Bash. -- `3` - Command-line Syntax Error. - -# Security - -See [SECURITY.md](./SECURITY.md). diff --git a/taskchampion/README.md b/taskchampion/README.md deleted file mode 100644 index ed7cfa77c..000000000 --- a/taskchampion/README.md +++ /dev/null @@ -1,50 +0,0 @@ -TaskChampion ------------- - -TaskChampion implements the task storage and synchronization behind Taskwarrior. -It includes an implementation with Rust and C APIs, allowing any application to maintain and manipulate its own replica. -It also includes a specification for tasks and how they are synchronized, inviting alternative implementations of replicas or task servers. - -See the [documentation](https://gothenburgbitfactory.github.io/taskwarrior/taskchampion/) for more! - -NOTE: Taskwarrior is currently in the midst of a change to use TaskChampion as its storage. -Until that is complete, the information here may be out-of-date. - -## Structure - -There are four crates here: - - * [taskchampion](./taskchampion) - the core of the tool - * [taskchampion-lib](./lib) - glue code to use _taskchampion_ from C - * [integration-tests](./integration-tests) (private) - integration tests covering _taskchampion_ and _taskchampion-lib_. - * [xtask](./xtask) (private) - implementation of the `cargo xtask codegen` command - -## Code Generation - -The _taskchampion_lib_ crate uses a bit of code generation to create the `lib/taskchampion.h` header file. -To regenerate this file, run `cargo xtask codegen`. - -## Rust API - -The Rust API, as defined in [the docs](https://docs.rs/taskchampion/latest/taskchampion/), supports simple creation and manipulation of replicas and the tasks they contain. - -The Rust API follows semantic versioning. -As this is still in the `0.x` phase, so breaking changes may occur but will be indicated with a change to the minor version. - -## C API - -The `taskchampion-lib` crate generates libraries suitable for use from C (or any C-compatible language). -It is a "normal" Cargo crate that happens to export a number of `extern "C"` symbols, and also contains a `taskchampion.h` defining those symbols. - -*WARNING: the C API is not yet stable!* - -It is your responsibility to link this into a form usable in your own build process. -For example, in a typical CMake C++ project, CMakeRust can do this for you. -In many cases, this is as simple as a rust crate with `src/lib.rs` containing - -```rust -pub use taskchampion_lib::*; -``` - -Arrange to use the header file, `lib/taskchampion.h`, by copying it or adding its directory to your include search path. -[Future work](https://github.com/GothenburgBitFactory/taskwarrior/issues/2870) will provide better automation for this process. diff --git a/taskchampion/RELEASING.md b/taskchampion/RELEASING.md deleted file mode 100644 index 78650fe0a..000000000 --- a/taskchampion/RELEASING.md +++ /dev/null @@ -1,16 +0,0 @@ -# Release process - -1. Ensure the changelog is updated with everything from the `.changelogs` directory. `python3 ./scripts/changelog.py build` will output a Markdown snippet to include in `CHANGELOG.md` then `rm .changelog/*.txt` -1. Run `git pull upstream main` -1. Run `cargo test` -1. Run `cargo clean && cargo clippy` -1. Run `mdbook test docs` -1. Update `version` in `*/Cargo.toml`. All versions should match. -1. Run `cargo build --release` -1. Commit the changes (Cargo.lock will change too) with comment `vX.Y.Z`. -1. Run `git tag vX.Y.Z` -1. Run `git push upstream` -1. Run `git push --tags upstream` -1. Run `(cd taskchampion; cargo publish)` (note that the other crates do not get published) -1. Navigate to the tag in the GitHub releases UI and create a release with general comments about the changes in the release -1. Upload `./target/release/task` and `./target/release/task-sync-server` to the release diff --git a/taskchampion/SECURITY.md b/taskchampion/SECURITY.md deleted file mode 100644 index 9d8d975d9..000000000 --- a/taskchampion/SECURITY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Security - -To report a vulnerability, please contact [dustin@cs.uchicago.edu](dustin@cs.uchicago.edu), you may use GPG public-key `D8097934A92E4B4210368102FF8B7AC6154E3226` which is available [here](https://keybase.io/djmitche/pgp_keys.asc?fingerprint=d8097934a92e4b4210368102ff8b7ac6154e3226). Initial response is expected within ~48h. - -We kindly ask to follow the responsible disclosure model and refrain from sharing information until: -1. Vulnerabilities are patched in TaskChampion + 60 days to coordinate with distributions. -2. 90 days since the vulnerability is disclosed to us. - -We recognise the legitimacy of public interest and accept that security researchers can publish information after 90-days deadline unilaterally. - -We will assist with obtaining CVE and acknowledge the vulnerabilites reported. diff --git a/taskchampion/docs/.gitignore b/taskchampion/docs/.gitignore deleted file mode 100644 index d2479eb14..000000000 --- a/taskchampion/docs/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -book -tmp diff --git a/taskchampion/docs/README.md b/taskchampion/docs/README.md deleted file mode 100644 index 7aaa35c16..000000000 --- a/taskchampion/docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This is an [mdbook](https://rust-lang.github.io/mdBook/index.html) book. -Minor modifications can be made without installing the mdbook tool, as the content is simple Markdown. -Changes are verified on pull requests. diff --git a/taskchampion/docs/assets/cgi/LICENSE.md b/taskchampion/docs/assets/cgi/LICENSE.md deleted file mode 100644 index 1d4dbe059..000000000 --- a/taskchampion/docs/assets/cgi/LICENSE.md +++ /dev/null @@ -1,2 +0,0 @@ -Copyright (C) Andrew Savchenko - All Rights Reserved -All files within this folder are proprietary and reserved for the use by TaskChampion project. diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_1024.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_1024.png deleted file mode 100755 index d4a4a9e13..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_1024.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_128.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_128.png deleted file mode 100755 index c6f6ccf87..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_128.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_16.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_16.png deleted file mode 100755 index 225b311de..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_16.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_256.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_256.png deleted file mode 100755 index 9717c8d70..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_256.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_32.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_32.png deleted file mode 100755 index 2832f0b3a..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_32.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_512.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_512.png deleted file mode 100755 index 986236490..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_512.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_64.png b/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_64.png deleted file mode 100755 index 1c831acce..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_rounded/icon_rounded_64.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_1024.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_1024.png deleted file mode 100755 index 275f3b206..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_1024.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_128.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_128.png deleted file mode 100755 index 2600bae3b..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_128.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_16.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_16.png deleted file mode 100755 index e11979d52..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_16.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_256.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_256.png deleted file mode 100755 index 25cf7f694..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_256.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_32.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_32.png deleted file mode 100755 index 24e9a6097..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_32.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_512.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_512.png deleted file mode 100755 index da117347a..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_512.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/icon_square/icon_square_64.png b/taskchampion/docs/assets/cgi/icon_square/icon_square_64.png deleted file mode 100755 index d9d63e823..000000000 Binary files a/taskchampion/docs/assets/cgi/icon_square/icon_square_64.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_1024.png b/taskchampion/docs/assets/cgi/logo/logo_1024.png deleted file mode 100755 index 8f2cf7724..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_1024.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_128.png b/taskchampion/docs/assets/cgi/logo/logo_128.png deleted file mode 100755 index c32d2abe4..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_128.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_16.png b/taskchampion/docs/assets/cgi/logo/logo_16.png deleted file mode 100755 index 867dda789..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_16.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_256.png b/taskchampion/docs/assets/cgi/logo/logo_256.png deleted file mode 100755 index a01735a0d..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_256.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_32.png b/taskchampion/docs/assets/cgi/logo/logo_32.png deleted file mode 100755 index b180de372..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_32.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_512.png b/taskchampion/docs/assets/cgi/logo/logo_512.png deleted file mode 100755 index 2a2fb1502..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_512.png and /dev/null differ diff --git a/taskchampion/docs/assets/cgi/logo/logo_64.png b/taskchampion/docs/assets/cgi/logo/logo_64.png deleted file mode 100755 index 47e028add..000000000 Binary files a/taskchampion/docs/assets/cgi/logo/logo_64.png and /dev/null differ diff --git a/taskchampion/docs/book.toml b/taskchampion/docs/book.toml deleted file mode 100644 index 7e2fa9820..000000000 --- a/taskchampion/docs/book.toml +++ /dev/null @@ -1,9 +0,0 @@ -[book] -authors = ["Dustin J. Mitchell"] -language = "en" -multilingual = false -src = "src" -title = "TaskChampion" - -[output.html] -default-theme = "ayu" diff --git a/taskchampion/docs/src/SUMMARY.md b/taskchampion/docs/src/SUMMARY.md deleted file mode 100644 index b5bc3c6e5..000000000 --- a/taskchampion/docs/src/SUMMARY.md +++ /dev/null @@ -1,17 +0,0 @@ -# Summary - -- [Installation](./installation.md) - * [Running the Sync Server](./running-sync-server.md) -- [Internal Details](./internals.md) - * [Data Model](./data-model.md) - * [Replica Storage](./storage.md) - * [Task Database](./taskdb.md) - * [Tasks](./tasks.md) - * [Synchronization and the Sync Server](./sync.md) - * [Synchronization Model](./sync-model.md) - * [Snapshots](./snapshots.md) - * [Server-Replica Protocol](./sync-protocol.md) - * [Encryption](./encryption.md) - * [HTTP Implementation](./http.md) - * [Object-Store Implementation](./object-store.md) - * [Planned Functionality](./plans.md) diff --git a/taskchampion/docs/src/data-model.md b/taskchampion/docs/src/data-model.md deleted file mode 100644 index 2a43df62b..000000000 --- a/taskchampion/docs/src/data-model.md +++ /dev/null @@ -1,5 +0,0 @@ -# Data Model - -A client manages a single offline instance of a single user's task list, called a replica. -This section covers the structure of that data. -Note that this data model is visible only on the client; the server does not have access to client data. diff --git a/taskchampion/docs/src/encryption.md b/taskchampion/docs/src/encryption.md deleted file mode 100644 index 8a1e57a8f..000000000 --- a/taskchampion/docs/src/encryption.md +++ /dev/null @@ -1,38 +0,0 @@ -# Encryption - -The client configuration includes an encryption secret of arbitrary length. -This section describes how that information is used to encrypt and decrypt data sent to the server (versions and snapshots). - -Encryption is not used for local (on-disk) sync, but is used for all cases where data is sent from the local host. - -## Key Derivation - -The client derives the 32-byte encryption key from the configured encryption secret using PBKDF2 with HMAC-SHA256 and 600,000 iterations. -The salt value depends on the implementation of the protocol, as described in subsequent chapters. - -## Encryption - -The client uses [AEAD](https://commondatastorage.googleapis.com/chromium-boringssl-docs/aead.h.html), with algorithm CHACHA20_POLY1305. -The client should generate a random nonce, noting that AEAD is _not secure_ if a nonce is used repeatedly for the same key. - -AEAD supports additional authenticated data (AAD) which must be provided for both open and seal operations. -In this protocol, the AAD is always 17 bytes of the form: - * `app_id` (byte) - always 1 - * `version_id` (16 bytes) - 16-byte form of the version ID associated with this data - * for versions (AddVersion, GetChildVersion), the _parent_ version_id - * for snapshots (AddSnapshot, GetSnapshot), the snapshot version_id - -The `app_id` field is for future expansion to handle other, non-task data using this protocol. -Including it in the AAD ensures that such data cannot be confused with task data. - -Although the AEAD specification distinguishes ciphertext and tags, for purposes of this specification they are considered concatenated into a single bytestring as in BoringSSL's `EVP_AEAD_CTX_seal`. - -## Representation - -The final byte-stream is comprised of the following structure: - -* `version` (byte) - format version (always 1) -* `nonce` (12 bytes) - encryption nonce -* `ciphertext` (remaining bytes) - ciphertext from sealing operation - -The `version` field identifies this data format, and future formats will have a value other than 1 in this position. diff --git a/taskchampion/docs/src/http.md b/taskchampion/docs/src/http.md deleted file mode 100644 index 90b93753d..000000000 --- a/taskchampion/docs/src/http.md +++ /dev/null @@ -1,65 +0,0 @@ -# HTTP Representation - -The transactions in the sync protocol are realized for an HTTP server at `` using the HTTP requests and responses described here. -The `origin` *should* be an HTTPS endpoint on general principle, but nothing in the functonality or security of the protocol depends on connection encryption. - -The replica identifies itself to the server using a `client_id` in the form of a UUID. -This value is passed with every request in the `X-Client-Id` header, in its dashed-hex format. - -The salt used in key derivation is the 16-byte client ID. - -## AddVersion - -The request is a `POST` to `/v1/client/add-version/`. -The request body contains the history segment, optionally encoded using any encoding supported by actix-web. -The content-type must be `application/vnd.taskchampion.history-segment`. - -The success response is a 200 OK with an empty body. -The new version ID appears in the `X-Version-Id` header. -If included, a snapshot request appears in the `X-Snapshot-Request` header with value `urgency=low` or `urgency=high`. - -On conflict, the response is a 409 CONFLICT with an empty body. -The expected parent version ID appears in the `X-Parent-Version-Id` header. - -Other error responses (4xx or 5xx) may be returned and should be treated appropriately to their meanings in the HTTP specification. - -## GetChildVersion - -The request is a `GET` to `/v1/client/get-child-version/`. - -The response is determined as described above. -The _not-found_ response is 404 NOT FOUND. -The _gone_ response is 410 GONE. -Neither has a response body. - -On success, the response is a 200 OK. -The version's history segment is returned in the response body, with content-type `application/vnd.taskchampion.history-segment`. -The version ID appears in the `X-Version-Id` header. -The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. - -On failure, a client should treat a 404 NOT FOUND as indicating that it is up-to-date. -Clients should treat a 410 GONE as a synchronization error. -If the client has pending changes to send to the server, based on a now-removed version, then those changes cannot be reconciled and will be lost. -The client should, optionally after consulting the user, download and apply the latest snapshot. - -## AddSnapshot - -The request is a `POST` to `/v1/client/add-snapshot/`. -The request body contains the snapshot data, optionally encoded using any encoding supported by actix-web. -The content-type must be `application/vnd.taskchampion.snapshot`. - -If the version is invalid, as described above, the response should be 400 BAD REQUEST. -The server response should be 200 OK on success. - -## GetSnapshot - -The request is a `GET` to `/v1/client/snapshot`. - -The response is a 200 OK. -The snapshot is returned in the response body, with content-type `application/vnd.taskchampion.snapshot`. -The version ID appears in the `X-Version-Id` header. -The response body may be encoded, in accordance with any `Accept-Encoding` header in the request. - -After downloading and decrypting a snapshot, a client must replace its entire local task database with the content of the snapshot. -Any local operations that had not yet been synchronized must be discarded. -After the snapshot is applied, the client should begin the synchronization process again, starting from the snapshot version. diff --git a/taskchampion/docs/src/images/name_timestamp.png b/taskchampion/docs/src/images/name_timestamp.png deleted file mode 100644 index d829039b7..000000000 Binary files a/taskchampion/docs/src/images/name_timestamp.png and /dev/null differ diff --git a/taskchampion/docs/src/installation.md b/taskchampion/docs/src/installation.md deleted file mode 100644 index a597a11da..000000000 --- a/taskchampion/docs/src/installation.md +++ /dev/null @@ -1,3 +0,0 @@ -# Installation - -As this is currently in development, installation is by cloning the repository and running "cargo build". diff --git a/taskchampion/docs/src/internals.md b/taskchampion/docs/src/internals.md deleted file mode 100644 index 7cc36ac29..000000000 --- a/taskchampion/docs/src/internals.md +++ /dev/null @@ -1,5 +0,0 @@ -# Internal Details - -The following sections get into the details of how TaskChampion works. -None of this information is necessary to use TaskChampion, but might be helpful in understanding its behavior. -Developers of TaskChampion and of tools that integrate with TaskChampion should be familiar with this information. diff --git a/taskchampion/docs/src/object-store.md b/taskchampion/docs/src/object-store.md deleted file mode 100644 index af6054ad3..000000000 --- a/taskchampion/docs/src/object-store.md +++ /dev/null @@ -1,9 +0,0 @@ -# Object Store Representation - -TaskChampion also supports use of a generic key-value store to synchronize replicas. - -In this case, the salt used in key derivation is a random 16-byte value, stored -in the object store and retrieved as needed. - -The details of the mapping from this protocol to keys and values are private to the implementation. -Other applications should not access the key-value store directly. diff --git a/taskchampion/docs/src/plans.md b/taskchampion/docs/src/plans.md deleted file mode 100644 index 4ee20c5d0..000000000 --- a/taskchampion/docs/src/plans.md +++ /dev/null @@ -1,35 +0,0 @@ -# Planned Functionality - -This section is a bit of a to-do list for additional functionality to add to the synchronzation system. -Each feature has some discussion of how it might be implemented. - -## Snapshots - -As designed, storage required on the server would grow with time, as would the time required for new clients to update to the latest version. -As an optimization, the server also stores "snapshots" containing a full copy of the task database at a given version. -Based on configurable heuristics, it may delete older operations and snapshots, as long as enough data remains for active clients to synchronize and for new clients to initialize. - -Since snapshots must be computed by clients, the server may "request" a snapshot when providing the latest version to a client. -This request comes with a number indicating how much it 'wants" the snapshot. -Clients which can easily generate and transmit a snapshot should be generous to the server, while clients with more limited resources can wait until the server's requests are more desperate. -The intent is, where possible, to request snapshots created on well-connected desktop clients over mobile and low-power clients. - -## Encryption and Signing - -From the server's perspective, all data except for version numbers are opaque binary blobs. -Clients encrypt and sign these blobs using a symmetric key known only to the clients. -This secures the data at-rest on the server. -Note that privacy is not complete, as the server still has some information about users, including source and frequency of synchronization transactions and size of those transactions. - -## Backups - -In this design, the server is little more than an authenticated storage for encrypted blobs provided by the client. -To allow for failure or data loss on the server, clients are expected to cache these blobs locally for a short time (a week), along with a server-provided HMAC signature. -When data loss is detected -- such as when a client expects the server to have a version N or higher, and the server only has N-1, the client can send those blobs to the server. -The server can validate the HMAC and, if successful, add the blobs to its datastore. - -## Expiration - -Deleted tasks remain in the task database, and are simply hidden in most views. -All tasks have an expiration time after which they may be flushed, preventing unbounded increase in task database size. -However, purging of a task does not satisfy the necessary OT guarantees, so some further formal design work is required before this is implemented. diff --git a/taskchampion/docs/src/running-sync-server.md b/taskchampion/docs/src/running-sync-server.md deleted file mode 100644 index e8b8c56ce..000000000 --- a/taskchampion/docs/src/running-sync-server.md +++ /dev/null @@ -1,11 +0,0 @@ -# Running the Sync Server - -> NOTE: TaskChampion is still in development and not yet feature-complete. -> The server is functional, but lacks any administrative features. - -Run `taskchampion-sync-server` to start the sync server. -Use `--port` to specify the port it should listen on, and `--data-dir` to specify the directory which it should store its data. -It only serves HTTP; the expectation is that a frontend proxy will be used for HTTPS support. - -The server has optional parameters `--snapshot-days` and `--snapshot-version`, giving the target number of days and versions, respectively, between snapshots of the client state. -The default values for these parameters are generally adequate. diff --git a/taskchampion/docs/src/snapshots.md b/taskchampion/docs/src/snapshots.md deleted file mode 100644 index 1ca134f34..000000000 --- a/taskchampion/docs/src/snapshots.md +++ /dev/null @@ -1,39 +0,0 @@ -# Snapshots - -The basic synchronization model described in the previous page has a few shortcomings: - * servers must store an ever-increasing quantity of versions - * a new replica must download all versions since the beginning (the nil UUID) in order to derive the current state - -Snapshots allow TaskChampion to avoid both of these issues. -A snapshot is a copy of the task database at a specific version. -It is created by a replica, encrypted, and stored on the server. -A new replica can simply download a recent snapshot and apply any additional versions synchronized since that snapshot was made. -Servers can delete and reclaim space used by older versions, as long as newer snapshots are available. - -## Snapshot Heuristics - -A server implementation must answer a few questions: - * How often should snapshots be made? - * When can versions be deleted? - * When can snapshots be deleted? - -A critical invariant is that at least one snapshot must exist for any database that does not have a child of the nil version. -This ensures that a new replica can always derive the latest state. - -Aside from that invariant, the server implementation can vary in its answers to these questions, with the following considerations: - -Snapshots should be made frequently enough that a new replica can initialize quickly. - -Existing replicas will fail to synchronize if they request a child version that has been deleted. -This failure can cause data loss if the replica had local changes. -It's conceivable that replicas may not sync for weeks or months if, for example, they are located on a home computer while the user is on holiday. - -## Requesting New Snapshots - -The server requests snapshots from replicas, indicating an urgency for the request. -Some replicas, such as those running on PCs or servers, can produce a snapshot even at low urgency. -Other replicas, in more restricted environments such as mobile devices, will only produce a snapshot at high urgency. -This saves resources in these restricted environments. - -A snapshot must be made on a replica with no unsynchronized operations. -As such, it only makes sense to request a snapshot in response to a successful AddVersion request. diff --git a/taskchampion/docs/src/storage.md b/taskchampion/docs/src/storage.md deleted file mode 100644 index f733f4f98..000000000 --- a/taskchampion/docs/src/storage.md +++ /dev/null @@ -1,83 +0,0 @@ -# Replica Storage - -Each replica has a storage backend. -The interface for this backend is given in `crate::taskstorage::Storage` and `StorageTxn`. - -The storage is transaction-protected, with the expectation of a serializable isolation level. -The storage contains the following information: - -- `tasks`: a set of tasks, indexed by UUID -- `base_version`: the number of the last version sync'd from the server (a single integer) -- `operations`: all operations performed since base_version -- `working_set`: a mapping from integer -> UUID, used to keep stable small-integer indexes into the tasks for users' convenience. This data is not synchronized with the server and does not affect any consistency guarantees. - -## Tasks - -The tasks are stored as an un-ordered collection, keyed by task UUID. -Each task in the database has represented by a key-value map. -See [Tasks](./tasks.md) for details on the content of that map. - -## Operations - -Every change to the task database is captured as an operation. -In other words, operations act as deltas between database states. -Operations are crucial to synchronization of replicas, described in [Synchronization Model](./sync-model.md). - -Operations are entirely managed by the replica, and some combinations of operations are described as "invalid" here. -A replica must not create invalid operations, but should be resilient to receiving invalid operations during a synchronization operation. - -Each operation has one of the forms - - * `Create(uuid)` - * `Delete(uuid, oldTask)` - * `Update(uuid, property, oldValue, newValue, timestamp)` - * `UndoPoint()` - -The Create form creates a new task. -It is invalid to create a task that already exists. - -Similarly, the Delete form deletes an existing task. -It is invalid to delete a task that does not exist. -The `oldTask` property contains the task data from before it was deleted. - -The Update form updates the given property of the given task, where the property and values are strings. -The `oldValue` gives the old value of the property (or None to create a new property), while `newValue` gives the new value (or None to delete a property). -It is invalid to update a task that does not exist. -The timestamp on updates serves as additional metadata and is used to resolve conflicts. - -### Application - -Each operation can be "applied" to a task database in a natural way: - - * Applying `Create` creates a new, empty task in the task database. - * Applying `Delete` deletes a task, including all of its properties, from the task database. - * Applying `Update` modifies the properties of a task. - * Applying `UndoPoint` does nothing. - -### Undo - -Each operation also contains enough information to reverse its application: - - * Undoing `Create` deletes a task. - * Undoing `Delete` creates a task, including all of the properties in `oldTask`. - * Undoing `Update` modifies the properties of a task, reverting to `oldValue`. - * Undoing `UndoPoint` does nothing. - -The `UndoPoint` operation serves as a marker of points in the operation sequence to which the user might wish to undo. -For example, creation of a new task with several properities involves several operations, but is a single step from the user's perspective. -An "undo" command reverses operations, removing them from the operations sequence, until it reaches an `UndoPoint` operation. - -### Synchronizing Operations - -After operations are synchronized to the server, they can no longer be undone. -As such, the [synchronization model](./sync-model.md) uses simpler operations. -Replica operations are converted to sync operations as follows: - - * `Create(uuid)` -> `Create(uuid)` (no change) - * `Delete(uuid, oldTask)` -> `Delete(uuid)` - * `Update(uuid, property, oldValue, newValue, timestamp)` -> `Update(uuid, property, newValue, timestamp)` - * `UndoPoint()` -> Ø (dropped from operation sequence) - -Once a sequence of operations has been synchronized, there is no need to store those operations on the replica. -The current implementation deletes operations at that time. -An alternative approach is to keep operations for existing tasks, and provide access to those operations as a "history" of modifications to the task. diff --git a/taskchampion/docs/src/sync-model.md b/taskchampion/docs/src/sync-model.md deleted file mode 100644 index f03626efb..000000000 --- a/taskchampion/docs/src/sync-model.md +++ /dev/null @@ -1,141 +0,0 @@ -# Synchronization Model - -The [task database](./taskdb.md) also implements synchronization. -Synchronization occurs between disconnected replicas, mediated by a server. -The replicas never communicate directly with one another. -The server does not have access to the task data; it sees only opaque blobs of data with a small amount of metadata. - -The synchronization process is a critical part of the task database's functionality, and it cannot function efficiently without occasional synchronization operations - -## Operational Transforms - -Synchronization is based on [operational transformation](https://en.wikipedia.org/wiki/Operational_transformation). -This section will assume some familiarity with the concept. - -## State and Operations - -At a given time, the set of tasks in a replica's storage is the essential "state" of that replica. -All modifications to that state occur via operations, as defined in [Replica Storage](./storage.md). -We can draw a network, or graph, with the nodes representing states and the edges representing operations. -For example: - -```text - o -- State: {abc-d123: 'get groceries', priority L} - | - | -- Operation: set abc-d123 priority to H - | - o -- State: {abc-d123: 'get groceries', priority H} -``` - -For those familiar with distributed version control systems, a state is analogous to a revision, while an operation is analogous to a commit. - -Fundamentally, synchronization involves all replicas agreeing on a single, linear sequence of operations and the state that those operations create. -Since the replicas are not connected, each may have additional operations that have been applied locally, but which have not yet been agreed on. -The synchronization process uses operational transformation to "linearize" those operations. - -This process is analogous (vaguely) to rebasing a sequence of Git commits. -Critically, though, operations cannot merge; in effect, the only option is rebasing. -Furthermore, once an operation has been sent to the server it cannot be changed; in effect, the server does not permit "force push". - -### Sync Operations - -The [Replica Storage](./storage.md) model contains additional information in its operations that is not included in operations synchronized to other replicas. -In this document, we will be discussing "sync operations" of the form - - * `Create(uuid)` - * `Delete(uuid)` - * `Update(uuid, property, value, timestamp)` - - -### Versions - -Occasionally, database states are given a name (that takes the form of a UUID). -The system as a whole (all replicas) constructs a branch-free sequence of versions and the operations that separate each version from the next. -The version with the nil UUID is implicitly the empty database. - -The server stores the operations to change a state from a "parent" version to a "child" version, and provides that information as needed to replicas. -Replicas use this information to update their local task databases, and to generate new versions to send to the server. - -Replicas generate a new version to transmit local changes to the server. -The changes are represented as a sequence of operations with the state resulting from the final operation corresponding to the version. -In order to keep the versions in a single sequence, the server will only accept a proposed version from a replica if its parent version matches the latest version on the server. - -In the non-conflict case (such as with a single replica), then, a replica's synchronization process involves gathering up the operations it has accumulated since its last synchronization; bundling those operations into a version; and sending that version to the server. - -### Replica Invariant - -The replica's [storage](./storage.md) contains the current state in `tasks`, the as-yet un-synchronized operations in `operations`, and the last version at which synchronization occurred in `base_version`. - -The replica's un-synchronized operations are already reflected in its local `tasks`, so the following invariant holds: - -> Applying `operations` to the set of tasks at `base_version` gives a set of tasks identical -> to `tasks`. - -### Transformation - -When the latest version on the server contains operations that are not present in the replica, then the states have diverged. -For example: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's local state - z| - o -- version N+1 -``` - -(diagram notation: `o` designates a state, lower-case letters designate operations, and versions are presented as if they were numbered sequentially) - -In this situation, the replica must "rebase" the local operations onto the latest version from the server and try again. -This process is performed using operational transformation (OT). -The result of this transformation is a sequence of operations based on the latest version, and a sequence of operations the replica can apply to its local task database to reach the same state -Continuing the example above, the resulting operations are shown with `'`: - -```text - o -- version N - w|\a - o o - x| \b - o o - y| \c - o o -- replica's intermediate local state - z| |w' - o-N+1 o - a'\ |x' - o o - b'\ |y' - o o - c'\|z' - o -- version N+2 -``` - -The replica applies w' through z' locally, and sends a' through c' to the server as the operations to generate version N+2. -Either path through this graph, a-b-c-w'-x'-y'-z' or a'-b'-c'-w-x-y-z, must generate *precisely* the same final state at version N+2. -Careful selection of the operations and the transformation function ensure this. - -See the comments in the source code for the details of how this transformation process is implemented. - -## Synchronization Process - -To perform a synchronization, the replica first requests the child version of `base_version` from the server (GetChildVersion). -It applies that version to its local `tasks`, rebases its local `operations` as described above, and updates `base_version`. -The replica repeats this process until the server indicates no additional child versions exist. -If there are no un-synchronized local operations, the process is complete. - -Otherwise, the replica creates a new version containing its local operations, giving its `base_version` as the parent version, and transmits that to the server (AddVersion). -In most cases, this will succeed, but if another replica has created a new version in the interim, then the new version will conflict with that other replica's new version and the server will respond with the new expected parent version. -In this case, the process repeats. -If the server indicates a conflict twice with the same expected base version, that is an indication that the replica has diverged (something serious has gone wrong). - -## Servers - -A replica depends on periodic synchronization for performant operation. -Without synchronization, its list of pending operations would grow indefinitely, and tasks could never be expired. -So all replicas, even "singleton" replicas which do not replicate task data with any other replica, must synchronize periodically. - -TaskChampion provides a `LocalServer` for this purpose. -It implements the `get_child_version` and `add_version` operations as described, storing data on-disk locally. diff --git a/taskchampion/docs/src/sync-protocol.md b/taskchampion/docs/src/sync-protocol.md deleted file mode 100644 index 98caf24e3..000000000 --- a/taskchampion/docs/src/sync-protocol.md +++ /dev/null @@ -1,115 +0,0 @@ -# Server-Replica Protocol - -The server-replica protocol is defined abstractly in terms of request/response transactions. - -The protocol builds on the model presented in the previous chapters, and in particular on the synchronization process. - -## Clients - -From the protocol's perspective, replicas accessing the same task history are indistinguishable, so this protocol uses the term "client" to refer generically to all replicas replicating a single task history. - -## Server - -A server implements the requests and responses described below. -Where the logic is implemented depends on the specific implementation of the protocol. - -For each client, the server is responsible for storing the task history, in the form of a branch-free sequence of versions. -It also stores the latest snapshot, if any exists. -From the server's perspective, snapshots and versions are opaque byte sequences. - -## Version Invariant - -The following invariant must always hold: - -> All versions are linked by parent-child relationships to form a single chain. -> That is, each version must have no more than one parent and one child, and no more than one version may have zero parents or zero children. - -## Data Formats - -Task data sent to the server is encrypted by the client, using the scheme described in the "Encryption" chapter. - -### Version - -The decrypted form of a version is a JSON array containing operations in the order they should be applied. -Each operation has the form `{TYPE: DATA}`, for example: - - * `[{"Create":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}]` - * `[{"Delete":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7"}}]` - * `[{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":"v","timestamp":"2021-10-11T12:47:07.188090948Z"}}]` - * `[{"Update":{"uuid":"56e0be07-c61f-494c-a54c-bdcfdd52d2a7","property":"prop","value":null,"timestamp":"2021-10-11T12:47:07.188090948Z"}}]` (to delete a property) - -Timestamps are in RFC3339 format with a `Z` suffix. - -### Snapshot - -The decrypted form of a snapshot is a JSON object mapping task IDs to task properties. -For example (pretty-printed for clarity): - -```json -{ - "56e0be07-c61f-494c-a54c-bdcfdd52d2a7": { - "description": "a task", - "priority": "H" - }, - "4b7ed904-f7b0-4293-8a10-ad452422c7b3": { - "description": "another task" - } -} -``` - -## Transactions - -All interactions between the client and server are defined in terms of request/response transactions, as described here. - -### AddVersion - -The AddVersion transaction requests that the server add a new version to the client's task history. -The request contains the following; - - * parent version ID, and - * encrypted version data. - -The server determines whether the new version is acceptable, atomically with respect to other requests for the same client. -If it has no versions for the client, it accepts the version. -If it already has one or more versions for the client, then it accepts the version only if the given parent version has no children, thereby maintaining the version invariant. - -If the version is accepted, the server generates a new version ID for it. -The version is added to the chain of versions for the client, and the new version ID is returned in the response to the client. -The response may also include a request for a snapshot, with associated urgency. - -If the version is not accepted, the server makes no changes, but responds to the client with a conflict indication containing the ID of the version which has no children. -The client may then "rebase" its operations and try again. -Note that if a client receives two conflict responses with the same parent version ID, it is an indication that the client's version history has diverged from that on the server. - -### GetChildVersion - -The GetChildVersion transaction is a read-only request for a version. -The request consists of a parent version ID. -The server searches its set of versions for a version with the given parent ID. -If found, it returns the version's - - * version ID, - * parent version ID (matching that in the request), and - * encrypted version data. - -If not found, it returns an indication that no such version exists. - -### AddSnapshot - -The AddSnapshot transaction requests that the server store a new snapshot, generated by the client. -The request contains the following: - - * version ID at which the snapshot was made, and - * encrypted snapshot data. - -The server may validate that the snapshot is for an existing version and is newer than any existing snapshot. -It may also validate that the snapshot is for a "recent" version (e.g., one of the last 5 versions). -If a snapshot already exists for the given version, the server may keep or discard the new snapshot but should return a success indication to the client. - -The server response is empty. - -### GetSnapshot - -The GetSnapshot transaction requests that the server provide the latest snapshot. -The response contains the snapshot version ID and the snapshot data, if those exist. - diff --git a/taskchampion/docs/src/sync.md b/taskchampion/docs/src/sync.md deleted file mode 100644 index fed75d17f..000000000 --- a/taskchampion/docs/src/sync.md +++ /dev/null @@ -1,7 +0,0 @@ -# Synchronization and the Sync Server - -This section covers *synchronization* of *replicas* containing the same set of tasks. -A replica is can perform all operations locally without connecting to a sync server, then share those operations with other replicas when it connects. -Sync is a critical feature of TaskChampion, allowing users to consult and update the same task list on multiple devices, without requiring constant connection. - -This is a complex topic, and the section is broken into several chapters, beginning at the lower levels of the implementation and working up. diff --git a/taskchampion/docs/src/taskdb.md b/taskchampion/docs/src/taskdb.md deleted file mode 100644 index b70e9bf3c..000000000 --- a/taskchampion/docs/src/taskdb.md +++ /dev/null @@ -1,32 +0,0 @@ -# Task Database - -The task database is a layer of abstraction above the replica storage layer, responsible for maintaining some important invariants. -While the storage is pluggable, there is only one implementation of the task database. - -## Reading Data - -The task database provides read access to the data in the replica's storage through a variety of methods on the struct. -Each read operation is executed in a transaction, so data may not be consistent between read operations. -In practice, this is not an issue for TaskChampion's purposes. - -## Working Set - -The task database maintains the working set. -The working set maps small integers to current tasks, for easy reference by command-line users. -This is done in such a way that the task numbers remain stable until the working set is rebuilt, at which point gaps in the numbering, such as for completed tasks, are removed by shifting all higher-numbered tasks downward. - -The working set is not replicated, and is not considered a part of any consistency guarantees in the task database. - -## Modifying Data - -Modifications to the data set are made by applying operations. -Operations are described in [Replica Storage](./storage.md). - -Each operation is added to the list of operations in the storage, and simultaneously applied to the tasks in that storage. -Operations are checked for validity as they are applied. - -## Deletion and Expiration - -Deletion of a task merely changes the task's status to "deleted", leaving it in the Task database. -Actual removal of tasks from the task database takes place as part of _expiration_, triggered by the user as part of a garbage-collection process. -Expiration removes tasks with a `modified` property more than 180 days in the past, by creating a `Delete(uuid)` operation. diff --git a/taskchampion/docs/src/tasks.md b/taskchampion/docs/src/tasks.md deleted file mode 100644 index bac90cc9d..000000000 --- a/taskchampion/docs/src/tasks.md +++ /dev/null @@ -1,58 +0,0 @@ -# Tasks - -Tasks are stored internally as a key/value map with string keys and values. -All fields are optional: the `Create` operation creates an empty task. -Display layers should apply appropriate defaults where necessary. - -## Atomicity - -The synchronization process does not support read-modify-write operations. -For example, suppose tags are updated by reading a list of tags, adding a tag, and writing the result back. -This would be captured as an `Update` operation containing the amended list of tags. -Suppose two such `Update` operations are made in different replicas and must be reconciled: - * `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag1", "2020-11-23T14:21:22Z")` - * `Update("d394be59-60e6-499e-b7e7-ca0142648409", "tags", "oldtag,newtag2", "2020-11-23T15:08:57Z")` - -The result of this reconciliation will be `oldtag,newtag2`, while the user almost certainly intended `oldtag,newtag1,newtag2`. - -The key names given below avoid this issue, allowing user updates such as adding a tag or deleting a dependency to be represented in a single `Update` operation. - -## Validity - -_Any_ key/value map is a valid task. -Consumers of task data must make a best effort to interpret any map, even if it contains apparently contradictory information. -For example, a task with status "completed" but no "end" key present should be interpreted as completed at an unknown time. - -## Representations - -Integers are stored in decimal notation. - -Timestamps are stored as UNIX epoch timestamps, in the form of an integer. - -## Keys - -The following keys, and key formats, are defined: - -* `status` - one of `P` for a pending task (the default), `C` for completed, `D` for deleted, or `R` for recurring -* `description` - the one-line summary of the task -* `modified` - the time of the last modification of this task -* `start` - the most recent time at which this task was started (a task with no `start` key is not active) -* `end` - if present, the time at which this task was completed or deleted (note that this key may not agree with `status`: it may be present for a pending task, or absent for a deleted or completed task) -* `tag_` - indicates this task has tag `` (value is ignored) -* `wait` - indicates the time before which this task should be hidden, as it is not actionable -* `entry` - the time at which the task was created -* `annotation_` - value is an annotation created at the given time; for example, `annotation_1693329505`. -* `dep_` - indicates this task depends on another task identified by ``; the value is ignored; for example, `dep_8c4fed9c-c0d2-40c2-936d-36fc44e084a0` - -Note that while TaskChampion recognizes "recurring" as a status, it does not implement recurrence directly. - -### UDAs - -Any unrecognized keys are treated as "user-defined attributes" (UDAs). -These attributes can be used to store additional data associated with a task. -For example, applications that synchronize tasks with other systems such as calendars or team planning services might store unique identifiers for those systems as UDAs. -The application defining a UDA defines the format of the value. - -UDAs _should_ have a namespaced structure of the form `.`, where `` identifies the application defining the UDA. -For example, a service named "DevSync" synchronizing tasks from GitHub might use UDAs like `devsync.github.issue-id`. -Note that many existing UDAs for Taskwarrior integrations do not follow this pattern; these are referred to as legacy UDAs. diff --git a/taskchampion/integration-tests/.gitignore b/taskchampion/integration-tests/.gitignore deleted file mode 100644 index 140a35ffe..000000000 --- a/taskchampion/integration-tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test-db -test-sync-server diff --git a/taskchampion/integration-tests/Cargo.toml b/taskchampion/integration-tests/Cargo.toml deleted file mode 100644 index 217961394..000000000 --- a/taskchampion/integration-tests/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "integration-tests" -version = "0.4.1" -authors = ["Dustin J. Mitchell "] -edition = "2021" -publish = false -build = "build.rs" - -[dependencies] -taskchampion = { path = "../taskchampion" } -taskchampion-lib = { path = "../lib" } - -[dev-dependencies] -anyhow.workspace = true -tempfile.workspace = true -pretty_assertions.workspace = true -lazy_static.workspace = true - -[build-dependencies] -cc.workspace = true diff --git a/taskchampion/integration-tests/README.md b/taskchampion/integration-tests/README.md deleted file mode 100644 index 34a308aa5..000000000 --- a/taskchampion/integration-tests/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Integration Tests for TaskChampion - -## "Regular" Tests - -Some of the tests in `tests/` are just regular integration tests. -Nothing exciting to see. - -## Bindings Tests - -The bindings tests are a bit more interesting, since they are written in C. -They are composed of a collection of "suites", each in one C file in `integration-tests/src/bindings_tests/`. -Each suite contains a number of tests (using [Unity](http://www.throwtheswitch.org/unity)) and an exported function named after the suite that returns an exit status (1 = failure). - -The build script (`integration-tests/build.rs`) builds these files into a library that is linked with the `integration_tests` library crate. -This crate contains a `bindings_tests` module with a pub function for each suite. - -Finally, the `integration-tests/tests/bindings.rs` test file calls each of those functions in a separate test case. - -### Adding Tests - -To add a test, select a suite and add a new test-case function. -Add a `RUN_TEST` invocation for your new function to the `.._tests` function at the bottom. -Keep the `RUN_TEST`s in the same order as the functions they call. - -### Adding Suites - -To add a suite, - -1. Add a new C file in `integration-tests/src/bindings_tests/`, based off of one of the others. -1. Add a the suite name to `suites` in `integration-tests/build.rs`. diff --git a/taskchampion/integration-tests/build.rs b/taskchampion/integration-tests/build.rs deleted file mode 100644 index dd10a997c..000000000 --- a/taskchampion/integration-tests/build.rs +++ /dev/null @@ -1,51 +0,0 @@ -use std::env; -use std::fs; -use std::path::Path; - -/// Build the Unity-based C test suite in `src/bindings_tests`, linking the result with this -/// package's library crate. -fn build_bindings_tests(suites: &[&'static str]) { - let mut build = cc::Build::new(); - build.include("../lib"); // include path for taskchampion.h - build.include("src/bindings_tests/unity"); - build.define("UNITY_OUTPUT_CHAR", "test_output"); - build.define( - "UNITY_OUTPUT_CHAR_HEADER_DECLARATION", - "test_output(char c)", - ); - - let mut files = vec![ - "src/bindings_tests/test.c".into(), - "src/bindings_tests/unity/unity.c".into(), - ]; - for suite in suites { - files.push(format!("src/bindings_tests/{}.c", suite)); - } - for file in files { - build.file(&file); - println!("cargo:rerun-if-changed={}", file); - } - println!("cargo:rerun-if-changed=../lib/taskchampion.h"); - - build.compile("bindings-tests"); -} - -/// Make `bindings_test_suites.rs` listing all of the test suites, for use in building the -/// bindings-test binary. -fn make_suite_file(suites: &[&'static str]) { - let out_dir = env::var_os("OUT_DIR").unwrap(); - let dest_path = Path::new(&out_dir).join("bindings_test_suites.rs"); - let mut content = String::new(); - for suite in suites { - content.push_str(format!("suite!({}_tests);\n", suite).as_ref()); - } - fs::write(dest_path, content).unwrap(); -} - -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - let suites = &["uuid", "string", "task", "replica"]; - build_bindings_tests(suites); - make_suite_file(suites); -} diff --git a/taskchampion/integration-tests/src/bindings_tests/mod.rs b/taskchampion/integration-tests/src/bindings_tests/mod.rs deleted file mode 100644 index 327ca83fe..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::fs; - -extern "C" { - // set up to send test output to TEST-OUTPUT - fn setup_output(); - // close the output file - fn finish_output(); -} - -// Each suite is represented by a _tests C function in .c. -// All of these C files are built into a library that is linked to the crate -- but not to test -// crates. So, this macro produces a "glue function" that calls the C function, and that can be -// called from test crates. -macro_rules! suite( - { $s:ident } => { - pub fn $s() -> (i32, String) { - extern "C" { - fn $s() -> i32; - } - unsafe { setup_output() }; - let res = unsafe { $s() }; - unsafe { finish_output() }; - let output = fs::read_to_string("TEST-OUTPUT") - .unwrap_or_else(|e| format!("could not open TEST-OUTPUT: {}", e)); - (res, output) - } - }; -); - -include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs")); diff --git a/taskchampion/integration-tests/src/bindings_tests/replica.c b/taskchampion/integration-tests/src/bindings_tests/replica.c deleted file mode 100644 index 0126791ff..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/replica.c +++ /dev/null @@ -1,330 +0,0 @@ -#include -#include -#include -#include "taskchampion.h" -#include "unity.h" - -// creating an in-memory replica does not crash -static void test_replica_creation(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NOT_NULL(rep); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - tc_replica_free(rep); -} - -// creating an on-disk replica does not crash -static void test_replica_creation_disk(void) { - TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), true, NULL); - TEST_ASSERT_NOT_NULL(rep); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - tc_replica_free(rep); -} - -// undo on an empty in-memory TCReplica does nothing -static void test_replica_undo_empty(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - int undone; - TCReplicaOpList undo_ops = tc_replica_get_undo_ops(rep); - int rv = tc_replica_commit_undo_ops(rep, undo_ops, &undone); - TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); - TEST_ASSERT_EQUAL(0, undone); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - tc_replica_free(rep); -} - -// adding an undo point succeeds -static void test_replica_add_undo_point(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_add_undo_point(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - tc_replica_free(rep); -} - -// working set operations succeed -static void test_replica_working_set(void) { - TCWorkingSet *ws; - TCTask *task1, *task2, *task3; - TCUuid uuid, uuid1, uuid2, uuid3; - - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - ws = tc_replica_working_set(rep); - TEST_ASSERT_EQUAL(0, tc_working_set_len(ws)); - tc_working_set_free(ws); - - task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task1")); - TEST_ASSERT_NOT_NULL(task1); - uuid1 = tc_task_get_uuid(task1); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true)); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task2")); - TEST_ASSERT_NOT_NULL(task2); - uuid2 = tc_task_get_uuid(task2); - - task3 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task3")); - TEST_ASSERT_NOT_NULL(task3); - uuid3 = tc_task_get_uuid(task3); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - // finish task2 to leave a "hole" - tc_task_to_mut(task2, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task2)); - tc_task_to_immut(task2); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false)); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - tc_task_free(task1); - tc_task_free(task2); - tc_task_free(task3); - - // working set should now be - // 0 -> None - // 1 -> uuid1 - // 2 -> None - // 3 -> uuid3 - ws = tc_replica_working_set(rep); - TEST_ASSERT_EQUAL(2, tc_working_set_len(ws)); - TEST_ASSERT_EQUAL(3, tc_working_set_largest_index(ws)); - - TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 0, &uuid)); - TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 1, &uuid)); - TEST_ASSERT_EQUAL_MEMORY(uuid1.bytes, uuid.bytes, sizeof(uuid)); - TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 2, &uuid)); - TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 3, &uuid)); - TEST_ASSERT_EQUAL_MEMORY(uuid3.bytes, uuid.bytes, sizeof(uuid)); - - TEST_ASSERT_EQUAL(1, tc_working_set_by_uuid(ws, uuid1)); - TEST_ASSERT_EQUAL(0, tc_working_set_by_uuid(ws, uuid2)); - TEST_ASSERT_EQUAL(3, tc_working_set_by_uuid(ws, uuid3)); - - tc_working_set_free(ws); - - TEST_ASSERT_EQUAL(18, tc_replica_num_local_operations(rep)); - - tc_replica_free(rep); -} - -// When tc_replica_commit_undo_ops is passed NULL for undone_out, it still succeeds -static void test_replica_undo_empty_null_undone_out(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - TCReplicaOpList undo_ops = tc_replica_get_undo_ops(rep); - int rv = tc_replica_commit_undo_ops(rep, undo_ops, NULL); - TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - tc_replica_free(rep); -} - -// creating a task succeeds and the resulting task looks good -static void test_replica_task_creation(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TCUuid uuid = tc_task_get_uuid(task); - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - TCString desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc.ptr); - TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc)); - tc_string_free(&desc); - - tc_task_free(task); - - // get the task again and verify it - task = tc_replica_get_task(rep, uuid); - TEST_ASSERT_NOT_NULL(task); - TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - tc_task_free(task); - - tc_replica_free(rep); -} - -static void test_replica_sync_local(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - mkdir("test-sync-server", 0755); // ignore error, if dir already exists - - TCString err; - TCServer *server = tc_server_new_local(tc_string_borrow("test-sync-server"), &err); - TEST_ASSERT_NOT_NULL(server); - TEST_ASSERT_NULL(err.ptr); - - int rv = tc_replica_sync(rep, server, false); - TEST_ASSERT_EQUAL(TC_RESULT_OK, rv); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - tc_server_free(server); - tc_replica_free(rep); - - // test error handling - server = tc_server_new_local(tc_string_borrow("/no/such/directory"), &err); - TEST_ASSERT_NULL(server); - TEST_ASSERT_NOT_NULL(err.ptr); - tc_string_free(&err); -} - -static void test_replica_remote_server(void) { - TCString err; - TCServer *server = tc_server_new_sync( - tc_string_borrow("http://tc.freecinc.com"), - tc_uuid_new_v4(), - tc_string_borrow("\xf0\x28\x8c\x28"), // NOTE: not utf-8 - &err); - TEST_ASSERT_NOT_NULL(server); - TEST_ASSERT_NULL(err.ptr); - - // can't actually do anything with this server! - - tc_server_free(server); -} - -// a replica with tasks in it returns an appropriate list of tasks and list of uuids -static void test_replica_all_tasks(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task1 = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("task1")); - TEST_ASSERT_NOT_NULL(task1); - TCUuid uuid1 = tc_task_get_uuid(task1); - tc_task_free(task1); - - TCTask *task2 = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("task2")); - TEST_ASSERT_NOT_NULL(task2); - TCUuid uuid2 = tc_task_get_uuid(task2); - tc_task_free(task2); - - { - TCTaskList tasks = tc_replica_all_tasks(rep); - TEST_ASSERT_NOT_NULL(tasks.items); - TEST_ASSERT_EQUAL(2, tasks.len); - - bool seen1, seen2 = false; - for (size_t i = 0; i < tasks.len; i++) { - TCTask *task = tasks.items[i]; - TCString descr = tc_task_get_description(task); - if (0 == strcmp(tc_string_content(&descr), "task1")) { - seen1 = true; - } else if (0 == strcmp(tc_string_content(&descr), "task2")) { - seen2 = true; - } - tc_string_free(&descr); - } - TEST_ASSERT_TRUE(seen1); - TEST_ASSERT_TRUE(seen2); - - tc_task_list_free(&tasks); - TEST_ASSERT_NULL(tasks.items); - } - - { - TCUuidList uuids = tc_replica_all_task_uuids(rep); - TEST_ASSERT_NOT_NULL(uuids.items); - TEST_ASSERT_EQUAL(2, uuids.len); - - bool seen1, seen2 = false; - for (size_t i = 0; i < uuids.len; i++) { - TCUuid uuid = uuids.items[i]; - if (0 == memcmp(&uuid1, &uuid, sizeof(TCUuid))) { - seen1 = true; - } else if (0 == memcmp(&uuid2, &uuid, sizeof(TCUuid))) { - seen2 = true; - } - } - TEST_ASSERT_TRUE(seen1); - TEST_ASSERT_TRUE(seen2); - - tc_uuid_list_free(&uuids); - TEST_ASSERT_NULL(uuids.items); - } - - tc_replica_free(rep); -} - -// importing a task succeeds and the resulting task looks good -static void test_replica_task_import(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCUuid uuid; - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); - TCTask *task = tc_replica_import_task_with_uuid(rep, uuid); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - TCString desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc.ptr); - TEST_ASSERT_EQUAL_STRING("", tc_string_content(&desc)); // default value - tc_string_free(&desc); - - tc_task_free(task); - - // get the task again and verify it - task = tc_replica_get_task(rep, uuid); - TEST_ASSERT_NOT_NULL(task); - TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes)); - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// importing a task succeeds and the resulting task looks good -static void test_replica_get_task_not_found(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCUuid uuid; - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid)); - TCTask *task = tc_replica_get_task(rep, uuid); - TEST_ASSERT_NULL(task); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - tc_replica_free(rep); -} - -int replica_tests(void) { - UNITY_BEGIN(); - // each test case above should be named here, in order. - RUN_TEST(test_replica_creation); - RUN_TEST(test_replica_creation_disk); - RUN_TEST(test_replica_undo_empty); - RUN_TEST(test_replica_add_undo_point); - RUN_TEST(test_replica_working_set); - RUN_TEST(test_replica_undo_empty_null_undone_out); - RUN_TEST(test_replica_task_creation); - RUN_TEST(test_replica_sync_local); - RUN_TEST(test_replica_remote_server); - RUN_TEST(test_replica_all_tasks); - RUN_TEST(test_replica_task_import); - RUN_TEST(test_replica_get_task_not_found); - return UNITY_END(); -} diff --git a/taskchampion/integration-tests/src/bindings_tests/string.c b/taskchampion/integration-tests/src/bindings_tests/string.c deleted file mode 100644 index 2bd2749c0..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/string.c +++ /dev/null @@ -1,125 +0,0 @@ -#include -#include -#include "unity.h" -#include "taskchampion.h" - -// creating strings does not crash -static void test_string_creation(void) { - TCString s = tc_string_borrow("abcdef"); - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// creating cloned strings does not crash -static void test_string_cloning(void) { - char *abcdef = strdup("abcdef"); - TCString s = tc_string_clone(abcdef); - TEST_ASSERT_NOT_NULL(s.ptr); - free(abcdef); - - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// creating cloned strings with invalid utf-8 does not crash -// ..but content is NULL and content_and_len returns the value -static void test_string_cloning_invalid_utf8(void) { - TCString s = tc_string_clone("\xf0\x28\x8c\x28"); - TEST_ASSERT_NOT_NULL(s.ptr); - - // NOTE: this is not one of the cases where invalid UTF-8 results in NULL, - // but that may change. - - size_t len; - const char *buf = tc_string_content_with_len(&s, &len); - TEST_ASSERT_NOT_NULL(buf); - TEST_ASSERT_EQUAL(4, len); - TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); - - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// borrowed strings echo back their content -static void test_string_borrowed_strings_echo(void) { - TCString s = tc_string_borrow("abcdef"); - TEST_ASSERT_NOT_NULL(s.ptr); - - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); - - size_t len; - const char *buf = tc_string_content_with_len(&s, &len); - TEST_ASSERT_NOT_NULL(buf); - TEST_ASSERT_EQUAL(6, len); - TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); - - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// cloned strings echo back their content -static void test_string_cloned_strings_echo(void) { - char *orig = strdup("abcdef"); - TCString s = tc_string_clone(orig); - TEST_ASSERT_NOT_NULL(s.ptr); - free(orig); - - TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s)); - - size_t len; - const char *buf = tc_string_content_with_len(&s, &len); - TEST_ASSERT_NOT_NULL(buf); - TEST_ASSERT_EQUAL(6, len); - TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len); - - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// tc_clone_with_len can have NULs, and tc_string_content returns NULL for -// strings containing embedded NULs -static void test_string_content_null_for_embedded_nuls(void) { - TCString s = tc_string_clone_with_len("ab\0de", 5); - TEST_ASSERT_NOT_NULL(s.ptr); - - TEST_ASSERT_NULL(tc_string_content(&s)); - - size_t len; - const char *buf = tc_string_content_with_len(&s, &len); - TEST_ASSERT_NOT_NULL(buf); - TEST_ASSERT_EQUAL(5, len); - TEST_ASSERT_EQUAL_MEMORY("ab\0de", buf, len); - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -// tc_string_clone_with_len will accept invalid utf-8, but then tc_string_content -// returns NULL. -static void test_string_clone_with_len_invalid_utf8(void) { - TCString s = tc_string_clone_with_len("\xf0\x28\x8c\x28", 4); - TEST_ASSERT_NOT_NULL(s.ptr); - - TEST_ASSERT_NULL(tc_string_content(&s)); - - size_t len; - const char *buf = tc_string_content_with_len(&s, &len); - TEST_ASSERT_NOT_NULL(buf); - TEST_ASSERT_EQUAL(4, len); - TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len); - tc_string_free(&s); - TEST_ASSERT_NULL(s.ptr); -} - -int string_tests(void) { - UNITY_BEGIN(); - // each test case above should be named here, in order. - RUN_TEST(test_string_creation); - RUN_TEST(test_string_cloning); - RUN_TEST(test_string_cloning_invalid_utf8); - RUN_TEST(test_string_borrowed_strings_echo); - RUN_TEST(test_string_cloned_strings_echo); - RUN_TEST(test_string_content_null_for_embedded_nuls); - RUN_TEST(test_string_clone_with_len_invalid_utf8); - return UNITY_END(); -} diff --git a/taskchampion/integration-tests/src/bindings_tests/task.c b/taskchampion/integration-tests/src/bindings_tests/task.c deleted file mode 100644 index 828775468..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/task.c +++ /dev/null @@ -1,717 +0,0 @@ -#include -#include -#include "unity.h" -#include "taskchampion.h" - -// creating a task succeeds and the resulting task looks good -static void test_task_creation(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - TCString desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc.ptr); - TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc)); - tc_string_free(&desc); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// freeing a mutable task works, marking it immutable -static void test_task_free_mutable_task(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - TCUuid uuid = tc_task_get_uuid(task); - - tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED)); - TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); - - tc_task_free(task); // implicitly converts to immut - - task = tc_replica_get_task(rep, uuid); - TEST_ASSERT_NOT_NULL(task); - TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating status on a task works -static void test_task_get_set_status(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - - tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED)); - TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut - tc_task_to_immut(task); - TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut - - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating description on a task works -static void test_task_get_set_description(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TCString desc; - - tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_description(task, tc_string_borrow("updated"))); - - desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc.ptr); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc)); - tc_string_free(&desc); - - tc_task_to_immut(task); - - desc = tc_task_get_description(task); - TEST_ASSERT_NOT_NULL(desc.ptr); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc)); - tc_string_free(&desc); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating arbitrary attributes on a task works -static void test_task_get_set_attribute(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TCString foo; - - foo = tc_task_get_value(task, tc_string_borrow("foo")); - TEST_ASSERT_NULL(foo.ptr); - - tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_value(task, - tc_string_borrow("foo"), - tc_string_borrow("updated"))); - - foo = tc_task_get_value(task, tc_string_borrow("foo")); - TEST_ASSERT_NOT_NULL(foo.ptr); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&foo)); - tc_string_free(&foo); - - tc_task_to_immut(task); - - foo = tc_task_get_value(task, tc_string_borrow("foo")); - TEST_ASSERT_NOT_NULL(foo.ptr); - TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&foo)); - tc_string_free(&foo); - - TCString null = { .ptr = NULL }; - - tc_task_to_mut(task, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_value(task, - tc_string_borrow("foo"), - null)); - - foo = tc_task_get_value(task, tc_string_borrow("foo")); - TEST_ASSERT_NULL(foo.ptr); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating entry on a task works -static void test_task_get_set_entry(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - // creation of a task sets entry to current time - TEST_ASSERT_NOT_EQUAL(0, tc_task_get_entry(task)); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 1643679997)); - TEST_ASSERT_EQUAL(1643679997, tc_task_get_entry(task)); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 0)); - TEST_ASSERT_EQUAL(0, tc_task_get_entry(task)); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating wait on a task works -static void test_task_get_set_wait_and_is_waiting(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - // wait is not set on creation - TEST_ASSERT_EQUAL(0, tc_task_get_wait(task)); - TEST_ASSERT_FALSE(tc_task_is_waiting(task)); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085 - TEST_ASSERT_EQUAL(3643679997, tc_task_get_wait(task)); - TEST_ASSERT_TRUE(tc_task_is_waiting(task)); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 643679997)); // THE PAST! - TEST_ASSERT_EQUAL(643679997, tc_task_get_wait(task)); - TEST_ASSERT_FALSE(tc_task_is_waiting(task)); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 0)); - TEST_ASSERT_EQUAL(0, tc_task_get_wait(task)); - TEST_ASSERT_FALSE(tc_task_is_waiting(task)); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// updating modified on a task works -static void test_task_get_set_modified(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - // creation of a task sets modified to current time - TEST_ASSERT_NOT_EQUAL(0, tc_task_get_modified(task)); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_modified(task, 1643679997)); - TEST_ASSERT_EQUAL(1643679997, tc_task_get_modified(task)); - - // zero is not allowed - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_set_modified(task, 0)); - - tc_task_free(task); - - tc_replica_free(rep); -} - -// starting and stopping a task works, as seen by tc_task_is_active -static void test_task_start_stop_is_active(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TEST_ASSERT_FALSE(tc_task_is_active(task)); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_FALSE(tc_task_is_active(task)); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_start(task)); - TEST_ASSERT_TRUE(tc_task_is_active(task)); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_stop(task)); - TEST_ASSERT_FALSE(tc_task_is_active(task)); - - tc_task_free(task); - tc_replica_free(rep); -} - -// tc_task_done and delete work and set the status -static void test_task_done_and_delete(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task)); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task)); - TEST_ASSERT_EQUAL(TC_STATUS_COMPLETED, tc_task_get_status(task)); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_delete(task)); - TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); - - tc_task_free(task); - tc_replica_free(rep); -} - -// adding and removing tags to a task works, and invalid tags are rejected -static void test_task_add_remove_has_tag(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - TEST_ASSERT_NULL(tc_task_error(task).ptr); - - TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); - - // invalid - synthetic tag - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING"))); - TCString err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err.ptr); - tc_string_free(&err); - - // invald - not a valid tag string - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("my tag"))); - err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err.ptr); - tc_string_free(&err); - - // invald - not utf-8 - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("\xf0\x28\x8c\x28"))); - err = tc_task_error(task); - TEST_ASSERT_NOT_NULL(err.ptr); - tc_string_free(&err); - - TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next"))); - - // remove the tag - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_tag(task, tc_string_borrow("next"))); - TEST_ASSERT_NULL(tc_task_error(task).ptr); - - TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next"))); - - tc_task_free(task); - tc_replica_free(rep); -} - -// get_tags returns the list of tags -static void test_task_get_tags(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - - TCStringList tags = tc_task_get_tags(task); - - int found_pending = false, found_next = false; - for (size_t i = 0; i < tags.len; i++) { - if (strcmp("PENDING", tc_string_content(&tags.items[i])) == 0) { - found_pending = true; - } - if (strcmp("next", tc_string_content(&tags.items[i])) == 0) { - found_next = true; - } - } - TEST_ASSERT_TRUE(found_pending); - TEST_ASSERT_TRUE(found_next); - - tc_string_list_free(&tags); - TEST_ASSERT_NULL(tags.items); - - tc_task_free(task); - tc_replica_free(rep); -} - -// annotation manipulation (add, remove, list, free) -static void test_task_annotations(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - TCAnnotationList anns = tc_task_get_annotations(task); - TEST_ASSERT_EQUAL(0, anns.len); - TEST_ASSERT_NOT_NULL(anns.items); - tc_annotation_list_free(&anns); - - tc_task_to_mut(task, rep); - - TCAnnotation ann; - - ann.entry = 1644623411; - ann.description = tc_string_borrow("ann1"); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); - TEST_ASSERT_NULL(ann.description.ptr); - - ann.entry = 1644623422; - ann.description = tc_string_borrow("ann2"); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); - TEST_ASSERT_NULL(ann.description.ptr); - - anns = tc_task_get_annotations(task); - - int found1 = false, found2 = false; - for (size_t i = 0; i < anns.len; i++) { - if (0 == strcmp("ann1", tc_string_content(&anns.items[i].description))) { - TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623411); - found1 = true; - } - if (0 == strcmp("ann2", tc_string_content(&anns.items[i].description))) { - TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623422); - found2 = true; - } - } - TEST_ASSERT_TRUE(found1); - TEST_ASSERT_TRUE(found2); - - tc_annotation_list_free(&anns); - TEST_ASSERT_NULL(anns.items); - - tc_task_free(task); - tc_replica_free(rep); -} - -// UDA manipulation (add, remove, list, free) -static void test_task_udas(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task( - rep, - TC_STATUS_PENDING, - tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - tc_task_to_mut(task, rep); - - TCString value; - TCUdaList udas; - - TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr); - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); - - udas = tc_task_get_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(0, udas.len); - tc_uda_list_free(&udas); - - udas = tc_task_get_legacy_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(0, udas.len); - tc_uda_list_free(&udas); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_set_uda(task, - tc_string_borrow("ns"), - tc_string_borrow("u1"), - tc_string_borrow("vvv"))); - - value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); - TEST_ASSERT_NOT_NULL(value.ptr); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value)); - tc_string_free(&value); - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); - - udas = tc_task_get_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(&udas.items[0].ns)); - TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(&udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value)); - tc_uda_list_free(&udas); - - udas = tc_task_get_legacy_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_NULL(udas.items[0].ns.ptr); - TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(&udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value)); - tc_uda_list_free(&udas); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_set_legacy_uda(task, - tc_string_borrow("leg1"), - tc_string_borrow("legv"))); - - value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")); - TEST_ASSERT_NOT_NULL(value.ptr); - TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value)); - tc_string_free(&value); - - value = tc_task_get_legacy_uda(task, tc_string_borrow("leg1")); - TEST_ASSERT_NOT_NULL(value.ptr); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&value)); - tc_string_free(&value); - - udas = tc_task_get_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(2, udas.len); - tc_uda_list_free(&udas); - - udas = tc_task_get_legacy_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(2, udas.len); - tc_uda_list_free(&udas); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_remove_uda(task, - tc_string_borrow("ns"), - tc_string_borrow("u1"))); - - TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_remove_uda(task, - tc_string_borrow("ns"), - tc_string_borrow("u1"))); - - udas = tc_task_get_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_EQUAL_STRING("", tc_string_content(&udas.items[0].ns)); - TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value)); - tc_uda_list_free(&udas); - - udas = tc_task_get_legacy_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(1, udas.len); - TEST_ASSERT_NULL(udas.items[0].ns.ptr); - TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key)); - TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value)); - tc_uda_list_free(&udas); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_remove_legacy_uda(task, - tc_string_borrow("leg1"))); - - TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_remove_legacy_uda(task, - tc_string_borrow("leg1"))); - - udas = tc_task_get_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(0, udas.len); - tc_uda_list_free(&udas); - - udas = tc_task_get_legacy_udas(task); - TEST_ASSERT_NOT_NULL(udas.items); - TEST_ASSERT_EQUAL(0, udas.len); - tc_uda_list_free(&udas); - - tc_task_free(task); - tc_replica_free(rep); -} - -// dependency manipulation -static void test_task_dependencies(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 1")); - TEST_ASSERT_NOT_NULL(task1); - TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task 2")); - TEST_ASSERT_NOT_NULL(task2); - - TCUuidList deps; - - deps = tc_task_get_dependencies(task1); - TEST_ASSERT_EQUAL(0, deps.len); - tc_uuid_list_free(&deps); - - tc_task_to_mut(task1, rep); - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_add_dependency(task1, tc_task_get_uuid(task2))); - - deps = tc_task_get_dependencies(task1); - TEST_ASSERT_EQUAL(1, deps.len); - TEST_ASSERT_EQUAL_MEMORY(tc_task_get_uuid(task2).bytes, deps.items[0].bytes, 16); - tc_uuid_list_free(&deps); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, - tc_task_remove_dependency(task1, tc_task_get_uuid(task2))); - - deps = tc_task_get_dependencies(task1); - TEST_ASSERT_EQUAL(0, deps.len); - tc_uuid_list_free(&deps); - - tc_task_free(task1); - tc_task_free(task2); - tc_replica_free(rep); -} - -static void tckvlist_assert_key(TCKVList *list, char *key, char *value) { - TEST_ASSERT_NOT_NULL(list); - for (size_t i = 0; i < list->len; i++) { - if (0 == strcmp(tc_string_content(&list->items[i].key), key)) { - TEST_ASSERT_EQUAL_STRING(value, tc_string_content(&list->items[i].value)); - return; - } - } - TEST_FAIL_MESSAGE("key not found"); -} - -// get_tags returns the list of tags -static void test_task_taskmap(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("my task")); - TEST_ASSERT_NOT_NULL(task); - - tc_task_to_mut(task, rep); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next"))); - - TCAnnotation ann; - ann.entry = 1644623411; - ann.description = tc_string_borrow("ann1"); - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann)); - - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085 - - TCKVList taskmap = tc_task_get_taskmap(task); - tckvlist_assert_key(&taskmap, "annotation_1644623411", "ann1"); - tckvlist_assert_key(&taskmap, "tag_next", ""); - tckvlist_assert_key(&taskmap, "status", "pending"); - tckvlist_assert_key(&taskmap, "description", "my task"); - tc_kv_list_free(&taskmap); - - tc_task_free(task); - tc_replica_free(rep); -} - -// taking from a task list behaves correctly -static void test_task_list_take(void) { - TCReplica *rep = tc_replica_new_in_memory(); - TEST_ASSERT_NULL(tc_replica_error(rep).ptr); - - TCTask *task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t")); - TEST_ASSERT_NOT_NULL(task1); - - TCTask *task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("t")); - TEST_ASSERT_NOT_NULL(task2); - tc_task_free(task2); - - TCString desc; - TCTaskList tasks = tc_replica_all_tasks(rep); - TEST_ASSERT_NOT_NULL(tasks.items); - TEST_ASSERT_EQUAL(2, tasks.len); - - task1 = tc_task_list_take(&tasks, 5); // out of bounds - TEST_ASSERT_NULL(task1); - - task1 = tc_task_list_take(&tasks, 0); - TEST_ASSERT_NOT_NULL(task1); - desc = tc_task_get_description(task1); - TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc)); - tc_string_free(&desc); - - task2 = tc_task_list_take(&tasks, 1); - TEST_ASSERT_NOT_NULL(task2); - desc = tc_task_get_description(task2); - TEST_ASSERT_EQUAL_STRING("t", tc_string_content(&desc)); - tc_string_free(&desc); - - tc_task_free(task1); - tc_task_free(task2); - - task1 = tc_task_list_take(&tasks, 0); // already taken - TEST_ASSERT_NULL(task1); - - task1 = tc_task_list_take(&tasks, 5); // out of bounds - TEST_ASSERT_NULL(task1); - - tc_task_list_free(&tasks); - TEST_ASSERT_NULL(tasks.items); - - tc_replica_free(rep); -} - -int task_tests(void) { - UNITY_BEGIN(); - // each test case above should be named here, in order. - RUN_TEST(test_task_creation); - RUN_TEST(test_task_free_mutable_task); - RUN_TEST(test_task_get_set_status); - RUN_TEST(test_task_get_set_description); - RUN_TEST(test_task_get_set_attribute); - RUN_TEST(test_task_get_set_entry); - RUN_TEST(test_task_get_set_modified); - RUN_TEST(test_task_get_set_wait_and_is_waiting); - RUN_TEST(test_task_start_stop_is_active); - RUN_TEST(test_task_done_and_delete); - RUN_TEST(test_task_add_remove_has_tag); - RUN_TEST(test_task_get_tags); - RUN_TEST(test_task_annotations); - RUN_TEST(test_task_udas); - RUN_TEST(test_task_dependencies); - RUN_TEST(test_task_taskmap); - RUN_TEST(test_task_list_take); - return UNITY_END(); -} diff --git a/taskchampion/integration-tests/src/bindings_tests/test.c b/taskchampion/integration-tests/src/bindings_tests/test.c deleted file mode 100644 index 5afa236ca..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/test.c +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include "unity.h" - -// these functions are shared between all test "suites" -// and cannot be customized per-suite. -void setUp(void) { } -void tearDown(void) { } - -static FILE *output = NULL; - -// Set up for test_output, writing output to "TEST-OUTPUT" in the -// current directory. The Rust test harness reads this file to get -// the output and display it only on failure. This is called by -// the Rust test harness -void setup_output(void) { - output = fopen("TEST-OUTPUT", "w"); -} - -// Close the output file. Called by the Rust test harness. -void finish_output(void) { - fclose(output); - output = NULL; -} - -// this replaces UNITY_OUTPUT_CHAR, and writes output to -// TEST-OUTPUT in the current directory; the Rust test harness -// will read this data if the test fails. -void test_output(char c) { - fputc(c, output); -} diff --git a/taskchampion/integration-tests/src/bindings_tests/unity/LICENSE.txt b/taskchampion/integration-tests/src/bindings_tests/unity/LICENSE.txt deleted file mode 100644 index b9a329dde..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/unity/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - -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/taskchampion/integration-tests/src/bindings_tests/unity/README.md b/taskchampion/integration-tests/src/bindings_tests/unity/README.md deleted file mode 100644 index 6f755ced0..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/unity/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Unity - -This directory contains the src from https://github.com/ThrowTheSwitch/Unity, revision 8ba01386008196a92ef4fdbdb0b00f2434c79563. diff --git a/taskchampion/integration-tests/src/bindings_tests/unity/unity.c b/taskchampion/integration-tests/src/bindings_tests/unity/unity.c deleted file mode 100644 index b88024875..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/unity/unity.c +++ /dev/null @@ -1,2119 +0,0 @@ -/* ========================================================================= - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -============================================================================ */ - -#include "unity.h" -#include - -#ifdef AVR -#include -#else -#define PROGMEM -#endif - -/* If omitted from header, declare overrideable prototypes here so they're ready for use */ -#ifdef UNITY_OMIT_OUTPUT_CHAR_HEADER_DECLARATION -void UNITY_OUTPUT_CHAR(int); -#endif - -/* Helpful macros for us to use here in Assert functions */ -#define UNITY_FAIL_AND_BAIL do { Unity.CurrentTestFailed = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) -#define UNITY_IGNORE_AND_BAIL do { Unity.CurrentTestIgnored = 1; UNITY_OUTPUT_FLUSH(); TEST_ABORT(); } while (0) -#define RETURN_IF_FAIL_OR_IGNORE do { if (Unity.CurrentTestFailed || Unity.CurrentTestIgnored) { TEST_ABORT(); } } while (0) - -struct UNITY_STORAGE_T Unity; - -#ifdef UNITY_OUTPUT_COLOR -const char PROGMEM UnityStrOk[] = "\033[42mOK\033[00m"; -const char PROGMEM UnityStrPass[] = "\033[42mPASS\033[00m"; -const char PROGMEM UnityStrFail[] = "\033[41mFAIL\033[00m"; -const char PROGMEM UnityStrIgnore[] = "\033[43mIGNORE\033[00m"; -#else -const char PROGMEM UnityStrOk[] = "OK"; -const char PROGMEM UnityStrPass[] = "PASS"; -const char PROGMEM UnityStrFail[] = "FAIL"; -const char PROGMEM UnityStrIgnore[] = "IGNORE"; -#endif -static const char PROGMEM UnityStrNull[] = "NULL"; -static const char PROGMEM UnityStrSpacer[] = ". "; -static const char PROGMEM UnityStrExpected[] = " Expected "; -static const char PROGMEM UnityStrWas[] = " Was "; -static const char PROGMEM UnityStrGt[] = " to be greater than "; -static const char PROGMEM UnityStrLt[] = " to be less than "; -static const char PROGMEM UnityStrOrEqual[] = "or equal to "; -static const char PROGMEM UnityStrNotEqual[] = " to be not equal to "; -static const char PROGMEM UnityStrElement[] = " Element "; -static const char PROGMEM UnityStrByte[] = " Byte "; -static const char PROGMEM UnityStrMemory[] = " Memory Mismatch."; -static const char PROGMEM UnityStrDelta[] = " Values Not Within Delta "; -static const char PROGMEM UnityStrPointless[] = " You Asked Me To Compare Nothing, Which Was Pointless."; -static const char PROGMEM UnityStrNullPointerForExpected[] = " Expected pointer to be NULL"; -static const char PROGMEM UnityStrNullPointerForActual[] = " Actual pointer was NULL"; -#ifndef UNITY_EXCLUDE_FLOAT -static const char PROGMEM UnityStrNot[] = "Not "; -static const char PROGMEM UnityStrInf[] = "Infinity"; -static const char PROGMEM UnityStrNegInf[] = "Negative Infinity"; -static const char PROGMEM UnityStrNaN[] = "NaN"; -static const char PROGMEM UnityStrDet[] = "Determinate"; -static const char PROGMEM UnityStrInvalidFloatTrait[] = "Invalid Float Trait"; -#endif -const char PROGMEM UnityStrErrShorthand[] = "Unity Shorthand Support Disabled"; -const char PROGMEM UnityStrErrFloat[] = "Unity Floating Point Disabled"; -const char PROGMEM UnityStrErrDouble[] = "Unity Double Precision Disabled"; -const char PROGMEM UnityStrErr64[] = "Unity 64-bit Support Disabled"; -static const char PROGMEM UnityStrBreaker[] = "-----------------------"; -static const char PROGMEM UnityStrResultsTests[] = " Tests "; -static const char PROGMEM UnityStrResultsFailures[] = " Failures "; -static const char PROGMEM UnityStrResultsIgnored[] = " Ignored "; -#ifndef UNITY_EXCLUDE_DETAILS -static const char PROGMEM UnityStrDetail1Name[] = UNITY_DETAIL1_NAME " "; -static const char PROGMEM UnityStrDetail2Name[] = " " UNITY_DETAIL2_NAME " "; -#endif -/*----------------------------------------------- - * Pretty Printers & Test Result Output Handlers - *-----------------------------------------------*/ - -/*-----------------------------------------------*/ -/* Local helper function to print characters. */ -static void UnityPrintChar(const char* pch) -{ - /* printable characters plus CR & LF are printed */ - if ((*pch <= 126) && (*pch >= 32)) - { - UNITY_OUTPUT_CHAR(*pch); - } - /* write escaped carriage returns */ - else if (*pch == 13) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('r'); - } - /* write escaped line feeds */ - else if (*pch == 10) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('n'); - } - /* unprintable characters are shown as codes */ - else - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)*pch, 2); - } -} - -/*-----------------------------------------------*/ -/* Local helper function to print ANSI escape strings e.g. "\033[42m". */ -#ifdef UNITY_OUTPUT_COLOR -static UNITY_UINT UnityPrintAnsiEscapeString(const char* string) -{ - const char* pch = string; - UNITY_UINT count = 0; - - while (*pch && (*pch != 'm')) - { - UNITY_OUTPUT_CHAR(*pch); - pch++; - count++; - } - UNITY_OUTPUT_CHAR('m'); - count++; - - return count; -} -#endif - -/*-----------------------------------------------*/ -void UnityPrint(const char* string) -{ - const char* pch = string; - - if (pch != NULL) - { - while (*pch) - { -#ifdef UNITY_OUTPUT_COLOR - /* print ANSI escape code */ - if ((*pch == 27) && (*(pch + 1) == '[')) - { - pch += UnityPrintAnsiEscapeString(pch); - continue; - } -#endif - UnityPrintChar(pch); - pch++; - } - } -} -/*-----------------------------------------------*/ -void UnityPrintLen(const char* string, const UNITY_UINT32 length) -{ - const char* pch = string; - - if (pch != NULL) - { - while (*pch && ((UNITY_UINT32)(pch - string) < length)) - { - /* printable characters plus CR & LF are printed */ - if ((*pch <= 126) && (*pch >= 32)) - { - UNITY_OUTPUT_CHAR(*pch); - } - /* write escaped carriage returns */ - else if (*pch == 13) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('r'); - } - /* write escaped line feeds */ - else if (*pch == 10) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('n'); - } - /* unprintable characters are shown as codes */ - else - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)*pch, 2); - } - pch++; - } - } -} - -/*-----------------------------------------------*/ -void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style) -{ - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if (style == UNITY_DISPLAY_STYLE_CHAR) - { - /* printable characters plus CR & LF are printed */ - UNITY_OUTPUT_CHAR('\''); - if ((number <= 126) && (number >= 32)) - { - UNITY_OUTPUT_CHAR((int)number); - } - /* write escaped carriage returns */ - else if (number == 13) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('r'); - } - /* write escaped line feeds */ - else if (number == 10) - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('n'); - } - /* unprintable characters are shown as codes */ - else - { - UNITY_OUTPUT_CHAR('\\'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)number, 2); - } - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrintNumber(number); - } - } - else if ((style & UNITY_DISPLAY_RANGE_UINT) == UNITY_DISPLAY_RANGE_UINT) - { - UnityPrintNumberUnsigned((UNITY_UINT)number); - } - else - { - UNITY_OUTPUT_CHAR('0'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)number, (char)((style & 0xF) * 2)); - } -} - -/*-----------------------------------------------*/ -void UnityPrintNumber(const UNITY_INT number_to_print) -{ - UNITY_UINT number = (UNITY_UINT)number_to_print; - - if (number_to_print < 0) - { - /* A negative number, including MIN negative */ - UNITY_OUTPUT_CHAR('-'); - number = (~number) + 1; - } - UnityPrintNumberUnsigned(number); -} - -/*----------------------------------------------- - * basically do an itoa using as little ram as possible */ -void UnityPrintNumberUnsigned(const UNITY_UINT number) -{ - UNITY_UINT divisor = 1; - - /* figure out initial divisor */ - while (number / divisor > 9) - { - divisor *= 10; - } - - /* now mod and print, then divide divisor */ - do - { - UNITY_OUTPUT_CHAR((char)('0' + (number / divisor % 10))); - divisor /= 10; - } while (divisor > 0); -} - -/*-----------------------------------------------*/ -void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print) -{ - int nibble; - char nibbles = nibbles_to_print; - - if ((unsigned)nibbles > UNITY_MAX_NIBBLES) - { - nibbles = UNITY_MAX_NIBBLES; - } - - while (nibbles > 0) - { - nibbles--; - nibble = (int)(number >> (nibbles * 4)) & 0x0F; - if (nibble <= 9) - { - UNITY_OUTPUT_CHAR((char)('0' + nibble)); - } - else - { - UNITY_OUTPUT_CHAR((char)('A' - 10 + nibble)); - } - } -} - -/*-----------------------------------------------*/ -void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number) -{ - UNITY_UINT current_bit = (UNITY_UINT)1 << (UNITY_INT_WIDTH - 1); - UNITY_INT32 i; - - for (i = 0; i < UNITY_INT_WIDTH; i++) - { - if (current_bit & mask) - { - if (current_bit & number) - { - UNITY_OUTPUT_CHAR('1'); - } - else - { - UNITY_OUTPUT_CHAR('0'); - } - } - else - { - UNITY_OUTPUT_CHAR('X'); - } - current_bit = current_bit >> 1; - } -} - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -/* - * This function prints a floating-point value in a format similar to - * printf("%.7g") on a single-precision machine or printf("%.9g") on a - * double-precision machine. The 7th digit won't always be totally correct - * in single-precision operation (for that level of accuracy, a more - * complicated algorithm would be needed). - */ -void UnityPrintFloat(const UNITY_DOUBLE input_number) -{ -#ifdef UNITY_INCLUDE_DOUBLE - static const int sig_digits = 9; - static const UNITY_INT32 min_scaled = 100000000; - static const UNITY_INT32 max_scaled = 1000000000; -#else - static const int sig_digits = 7; - static const UNITY_INT32 min_scaled = 1000000; - static const UNITY_INT32 max_scaled = 10000000; -#endif - - UNITY_DOUBLE number = input_number; - - /* print minus sign (does not handle negative zero) */ - if (number < 0.0f) - { - UNITY_OUTPUT_CHAR('-'); - number = -number; - } - - /* handle zero, NaN, and +/- infinity */ - if (number == 0.0f) - { - UnityPrint("0"); - } - else if (isnan(number)) - { - UnityPrint("nan"); - } - else if (isinf(number)) - { - UnityPrint("inf"); - } - else - { - UNITY_INT32 n_int = 0; - UNITY_INT32 n; - int exponent = 0; - int decimals; - int digits; - char buf[16] = {0}; - - /* - * Scale up or down by powers of 10. To minimize rounding error, - * start with a factor/divisor of 10^10, which is the largest - * power of 10 that can be represented exactly. Finally, compute - * (exactly) the remaining power of 10 and perform one more - * multiplication or division. - */ - if (number < 1.0f) - { - UNITY_DOUBLE factor = 1.0f; - - while (number < (UNITY_DOUBLE)max_scaled / 1e10f) { number *= 1e10f; exponent -= 10; } - while (number * factor < (UNITY_DOUBLE)min_scaled) { factor *= 10.0f; exponent--; } - - number *= factor; - } - else if (number > (UNITY_DOUBLE)max_scaled) - { - UNITY_DOUBLE divisor = 1.0f; - - while (number > (UNITY_DOUBLE)min_scaled * 1e10f) { number /= 1e10f; exponent += 10; } - while (number / divisor > (UNITY_DOUBLE)max_scaled) { divisor *= 10.0f; exponent++; } - - number /= divisor; - } - else - { - /* - * In this range, we can split off the integer part before - * doing any multiplications. This reduces rounding error by - * freeing up significant bits in the fractional part. - */ - UNITY_DOUBLE factor = 1.0f; - n_int = (UNITY_INT32)number; - number -= (UNITY_DOUBLE)n_int; - - while (n_int < min_scaled) { n_int *= 10; factor *= 10.0f; exponent--; } - - number *= factor; - } - - /* round to nearest integer */ - n = ((UNITY_INT32)(number + number) + 1) / 2; - -#ifndef UNITY_ROUND_TIES_AWAY_FROM_ZERO - /* round to even if exactly between two integers */ - if ((n & 1) && (((UNITY_DOUBLE)n - number) == 0.5f)) - n--; -#endif - - n += n_int; - - if (n >= max_scaled) - { - n = min_scaled; - exponent++; - } - - /* determine where to place decimal point */ - decimals = ((exponent <= 0) && (exponent >= -(sig_digits + 3))) ? (-exponent) : (sig_digits - 1); - exponent += decimals; - - /* truncate trailing zeroes after decimal point */ - while ((decimals > 0) && ((n % 10) == 0)) - { - n /= 10; - decimals--; - } - - /* build up buffer in reverse order */ - digits = 0; - while ((n != 0) || (digits <= decimals)) - { - buf[digits++] = (char)('0' + n % 10); - n /= 10; - } - - /* print out buffer (backwards) */ - while (digits > 0) - { - if (digits == decimals) - { - UNITY_OUTPUT_CHAR('.'); - } - UNITY_OUTPUT_CHAR(buf[--digits]); - } - - /* print exponent if needed */ - if (exponent != 0) - { - UNITY_OUTPUT_CHAR('e'); - - if (exponent < 0) - { - UNITY_OUTPUT_CHAR('-'); - exponent = -exponent; - } - else - { - UNITY_OUTPUT_CHAR('+'); - } - - digits = 0; - while ((exponent != 0) || (digits < 2)) - { - buf[digits++] = (char)('0' + exponent % 10); - exponent /= 10; - } - while (digits > 0) - { - UNITY_OUTPUT_CHAR(buf[--digits]); - } - } - } -} -#endif /* ! UNITY_EXCLUDE_FLOAT_PRINT */ - -/*-----------------------------------------------*/ -static void UnityTestResultsBegin(const char* file, const UNITY_LINE_TYPE line) -{ -#ifdef UNITY_OUTPUT_FOR_ECLIPSE - UNITY_OUTPUT_CHAR('('); - UnityPrint(file); - UNITY_OUTPUT_CHAR(':'); - UnityPrintNumber((UNITY_INT)line); - UNITY_OUTPUT_CHAR(')'); - UNITY_OUTPUT_CHAR(' '); - UnityPrint(Unity.CurrentTestName); - UNITY_OUTPUT_CHAR(':'); -#else -#ifdef UNITY_OUTPUT_FOR_IAR_WORKBENCH - UnityPrint("'); - UnityPrint(Unity.CurrentTestName); - UnityPrint(" "); -#else -#ifdef UNITY_OUTPUT_FOR_QT_CREATOR - UnityPrint("file://"); - UnityPrint(file); - UNITY_OUTPUT_CHAR(':'); - UnityPrintNumber((UNITY_INT)line); - UNITY_OUTPUT_CHAR(' '); - UnityPrint(Unity.CurrentTestName); - UNITY_OUTPUT_CHAR(':'); -#else - UnityPrint(file); - UNITY_OUTPUT_CHAR(':'); - UnityPrintNumber((UNITY_INT)line); - UNITY_OUTPUT_CHAR(':'); - UnityPrint(Unity.CurrentTestName); - UNITY_OUTPUT_CHAR(':'); -#endif -#endif -#endif -} - -/*-----------------------------------------------*/ -static void UnityTestResultsFailBegin(const UNITY_LINE_TYPE line) -{ - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrFail); - UNITY_OUTPUT_CHAR(':'); -} - -/*-----------------------------------------------*/ -void UnityConcludeTest(void) -{ - if (Unity.CurrentTestIgnored) - { - Unity.TestIgnores++; - } - else if (!Unity.CurrentTestFailed) - { - UnityTestResultsBegin(Unity.TestFile, Unity.CurrentTestLineNumber); - UnityPrint(UnityStrPass); - } - else - { - Unity.TestFailures++; - } - - Unity.CurrentTestFailed = 0; - Unity.CurrentTestIgnored = 0; - UNITY_PRINT_EXEC_TIME(); - UNITY_PRINT_EOL(); - UNITY_FLUSH_CALL(); -} - -/*-----------------------------------------------*/ -static void UnityAddMsgIfSpecified(const char* msg) -{ - if (msg) - { - UnityPrint(UnityStrSpacer); - -#ifdef UNITY_PRINT_TEST_CONTEXT - UNITY_PRINT_TEST_CONTEXT(); -#endif -#ifndef UNITY_EXCLUDE_DETAILS - if (Unity.CurrentDetail1) - { - UnityPrint(UnityStrDetail1Name); - UnityPrint(Unity.CurrentDetail1); - if (Unity.CurrentDetail2) - { - UnityPrint(UnityStrDetail2Name); - UnityPrint(Unity.CurrentDetail2); - } - UnityPrint(UnityStrSpacer); - } -#endif - UnityPrint(msg); - } -} - -/*-----------------------------------------------*/ -static void UnityPrintExpectedAndActualStrings(const char* expected, const char* actual) -{ - UnityPrint(UnityStrExpected); - if (expected != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrint(expected); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } - UnityPrint(UnityStrWas); - if (actual != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrint(actual); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } -} - -/*-----------------------------------------------*/ -static void UnityPrintExpectedAndActualStringsLen(const char* expected, - const char* actual, - const UNITY_UINT32 length) -{ - UnityPrint(UnityStrExpected); - if (expected != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrintLen(expected, length); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } - UnityPrint(UnityStrWas); - if (actual != NULL) - { - UNITY_OUTPUT_CHAR('\''); - UnityPrintLen(actual, length); - UNITY_OUTPUT_CHAR('\''); - } - else - { - UnityPrint(UnityStrNull); - } -} - -/*----------------------------------------------- - * Assertion & Control Helpers - *-----------------------------------------------*/ - -/*-----------------------------------------------*/ -static int UnityIsOneArrayNull(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_LINE_TYPE lineNumber, - const char* msg) -{ - /* Both are NULL or same pointer */ - if (expected == actual) { return 0; } - - /* print and return true if just expected is NULL */ - if (expected == NULL) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrNullPointerForExpected); - UnityAddMsgIfSpecified(msg); - return 1; - } - - /* print and return true if just actual is NULL */ - if (actual == NULL) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrNullPointerForActual); - UnityAddMsgIfSpecified(msg); - return 1; - } - - return 0; /* return false if neither is NULL */ -} - -/*----------------------------------------------- - * Assertion Functions - *-----------------------------------------------*/ - -/*-----------------------------------------------*/ -void UnityAssertBits(const UNITY_INT mask, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if ((mask & expected) != (mask & actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)expected); - UnityPrint(UnityStrWas); - UnityPrintMask((UNITY_UINT)mask, (UNITY_UINT)actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualNumber(const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if (expected != actual) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expected, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, - const UNITY_INT actual, - const UNITY_COMPARISON_T compare, - const char *msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - int failed = 0; - RETURN_IF_FAIL_OR_IGNORE; - - if ((threshold == actual) && (compare & UNITY_EQUAL_TO)) { return; } - if ((threshold == actual)) { failed = 1; } - - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if ((actual > threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } - if ((actual < threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } - } - else /* UINT or HEX */ - { - if (((UNITY_UINT)actual > (UNITY_UINT)threshold) && (compare & UNITY_SMALLER_THAN)) { failed = 1; } - if (((UNITY_UINT)actual < (UNITY_UINT)threshold) && (compare & UNITY_GREATER_THAN)) { failed = 1; } - } - - if (failed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(actual, style); - if (compare & UNITY_GREATER_THAN) { UnityPrint(UnityStrGt); } - if (compare & UNITY_SMALLER_THAN) { UnityPrint(UnityStrLt); } - if (compare & UNITY_EQUAL_TO) { UnityPrint(UnityStrOrEqual); } - if (compare == UNITY_NOT_EQUAL) { UnityPrint(UnityStrNotEqual); } - UnityPrintNumberByStyle(threshold, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#define UnityPrintPointlessAndBail() \ -do { \ - UnityTestResultsFailBegin(lineNumber); \ - UnityPrint(UnityStrPointless); \ - UnityAddMsgIfSpecified(msg); \ - UNITY_FAIL_AND_BAIL; \ -} while (0) - -/*-----------------------------------------------*/ -void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - unsigned int length = style & 0xF; - unsigned int increment = 0; - - RETURN_IF_FAIL_OR_IGNORE; - - if (num_elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - while ((elements > 0) && (elements--)) - { - UNITY_INT expect_val; - UNITY_INT actual_val; - - switch (length) - { - case 1: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; - increment = sizeof(UNITY_INT8); - break; - - case 2: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; - increment = sizeof(UNITY_INT16); - break; - -#ifdef UNITY_SUPPORT_64 - case 8: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; - increment = sizeof(UNITY_INT64); - break; -#endif - - default: /* default is length 4 bytes */ - case 4: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; - increment = sizeof(UNITY_INT32); - length = 4; - break; - } - - if (expect_val != actual_val) - { - if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) - { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ - UNITY_INT mask = 1; - mask = (mask << 8 * length) - 1; - expect_val &= mask; - actual_val &= mask; - } - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expect_val, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual_val, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - /* Walk through array by incrementing the pointers */ - if (flags == UNITY_ARRAY_TO_ARRAY) - { - expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); - } - actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); - } -} - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_FLOAT -/* Wrap this define in a function with variable types as float or double */ -#define UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff) \ - if (isinf(expected) && isinf(actual) && (((expected) < 0) == ((actual) < 0))) return 1; \ - if (UNITY_NAN_CHECK) return 1; \ - (diff) = (actual) - (expected); \ - if ((diff) < 0) (diff) = -(diff); \ - if ((delta) < 0) (delta) = -(delta); \ - return !(isnan(diff) || isinf(diff) || ((diff) > (delta))) - /* This first part of this condition will catch any NaN or Infinite values */ -#ifndef UNITY_NAN_NOT_EQUAL_NAN - #define UNITY_NAN_CHECK isnan(expected) && isnan(actual) -#else - #define UNITY_NAN_CHECK 0 -#endif - -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ - do { \ - UnityPrint(UnityStrExpected); \ - UnityPrintFloat(expected); \ - UnityPrint(UnityStrWas); \ - UnityPrintFloat(actual); \ - } while (0) -#else - #define UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual) \ - UnityPrint(UnityStrDelta) -#endif /* UNITY_EXCLUDE_FLOAT_PRINT */ - -/*-----------------------------------------------*/ -static int UnityFloatsWithin(UNITY_FLOAT delta, UNITY_FLOAT expected, UNITY_FLOAT actual) -{ - UNITY_FLOAT diff; - UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); -} - -/*-----------------------------------------------*/ -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_expected = expected; - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* ptr_actual = actual; - - RETURN_IF_FAIL_OR_IGNORE; - - if (elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - while (elements--) - { - if (!UnityFloatsWithin(*ptr_expected * UNITY_FLOAT_PRECISION, *ptr_expected, *ptr_actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)*ptr_expected, (UNITY_DOUBLE)*ptr_actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - if (flags == UNITY_ARRAY_TO_ARRAY) - { - ptr_expected++; - } - ptr_actual++; - } -} - -/*-----------------------------------------------*/ -void UnityAssertFloatsWithin(const UNITY_FLOAT delta, - const UNITY_FLOAT expected, - const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - - if (!UnityFloatsWithin(delta, expected, actual)) - { - UnityTestResultsFailBegin(lineNumber); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT((UNITY_DOUBLE)expected, (UNITY_DOUBLE)actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertFloatSpecial(const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style) -{ - const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; - UNITY_INT should_be_trait = ((UNITY_INT)style & 1); - UNITY_INT is_trait = !should_be_trait; - UNITY_INT trait_index = (UNITY_INT)(style >> 1); - - RETURN_IF_FAIL_OR_IGNORE; - - switch (style) - { - case UNITY_FLOAT_IS_INF: - case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); - break; - case UNITY_FLOAT_IS_NEG_INF: - case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); - break; - - case UNITY_FLOAT_IS_NAN: - case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; - break; - - case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ - case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); - break; - - default: /* including UNITY_FLOAT_INVALID_TRAIT */ - trait_index = 0; - trait_names[0] = UnityStrInvalidFloatTrait; - break; - } - - if (is_trait != should_be_trait) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - if (!should_be_trait) - { - UnityPrint(UnityStrNot); - } - UnityPrint(trait_names[trait_index]); - UnityPrint(UnityStrWas); -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - UnityPrintFloat((UNITY_DOUBLE)actual); -#else - if (should_be_trait) - { - UnityPrint(UnityStrNot); - } - UnityPrint(trait_names[trait_index]); -#endif - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#endif /* not UNITY_EXCLUDE_FLOAT */ - -/*-----------------------------------------------*/ -#ifndef UNITY_EXCLUDE_DOUBLE -static int UnityDoublesWithin(UNITY_DOUBLE delta, UNITY_DOUBLE expected, UNITY_DOUBLE actual) -{ - UNITY_DOUBLE diff; - UNITY_FLOAT_OR_DOUBLE_WITHIN(delta, expected, actual, diff); -} - -/*-----------------------------------------------*/ -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_expected = expected; - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* ptr_actual = actual; - - RETURN_IF_FAIL_OR_IGNORE; - - if (elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - while (elements--) - { - if (!UnityDoublesWithin(*ptr_expected * UNITY_DOUBLE_PRECISION, *ptr_expected, *ptr_actual)) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(*ptr_expected, *ptr_actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - if (flags == UNITY_ARRAY_TO_ARRAY) - { - ptr_expected++; - } - ptr_actual++; - } -} - -/*-----------------------------------------------*/ -void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, - const UNITY_DOUBLE expected, - const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if (!UnityDoublesWithin(delta, expected, actual)) - { - UnityTestResultsFailBegin(lineNumber); - UNITY_PRINT_EXPECTED_AND_ACTUAL_FLOAT(expected, actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style) -{ - const char* trait_names[] = {UnityStrInf, UnityStrNegInf, UnityStrNaN, UnityStrDet}; - UNITY_INT should_be_trait = ((UNITY_INT)style & 1); - UNITY_INT is_trait = !should_be_trait; - UNITY_INT trait_index = (UNITY_INT)(style >> 1); - - RETURN_IF_FAIL_OR_IGNORE; - - switch (style) - { - case UNITY_FLOAT_IS_INF: - case UNITY_FLOAT_IS_NOT_INF: - is_trait = isinf(actual) && (actual > 0); - break; - case UNITY_FLOAT_IS_NEG_INF: - case UNITY_FLOAT_IS_NOT_NEG_INF: - is_trait = isinf(actual) && (actual < 0); - break; - - case UNITY_FLOAT_IS_NAN: - case UNITY_FLOAT_IS_NOT_NAN: - is_trait = isnan(actual) ? 1 : 0; - break; - - case UNITY_FLOAT_IS_DET: /* A determinate number is non infinite and not NaN. */ - case UNITY_FLOAT_IS_NOT_DET: - is_trait = !isinf(actual) && !isnan(actual); - break; - - default: /* including UNITY_FLOAT_INVALID_TRAIT */ - trait_index = 0; - trait_names[0] = UnityStrInvalidFloatTrait; - break; - } - - if (is_trait != should_be_trait) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrExpected); - if (!should_be_trait) - { - UnityPrint(UnityStrNot); - } - UnityPrint(trait_names[trait_index]); - UnityPrint(UnityStrWas); -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - UnityPrintFloat(actual); -#else - if (should_be_trait) - { - UnityPrint(UnityStrNot); - } - UnityPrint(trait_names[trait_index]); -#endif - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -#endif /* not UNITY_EXCLUDE_DOUBLE */ - -/*-----------------------------------------------*/ -void UnityAssertNumbersWithin(const UNITY_UINT delta, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style) -{ - RETURN_IF_FAIL_OR_IGNORE; - - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if (actual > expected) - { - Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); - } - else - { - Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); - } - } - else - { - if ((UNITY_UINT)actual > (UNITY_UINT)expected) - { - Unity.CurrentTestFailed = (((UNITY_UINT)actual - (UNITY_UINT)expected) > delta); - } - else - { - Unity.CurrentTestFailed = (((UNITY_UINT)expected - (UNITY_UINT)actual) > delta); - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrDelta); - UnityPrintNumberByStyle((UNITY_INT)delta, style); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expected, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, - UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 elements = num_elements; - unsigned int length = style & 0xF; - unsigned int increment = 0; - - RETURN_IF_FAIL_OR_IGNORE; - - if (num_elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - while ((elements > 0) && (elements--)) - { - UNITY_INT expect_val; - UNITY_INT actual_val; - - switch (length) - { - case 1: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT8*)actual; - increment = sizeof(UNITY_INT8); - break; - - case 2: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT16*)actual; - increment = sizeof(UNITY_INT16); - break; - -#ifdef UNITY_SUPPORT_64 - case 8: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT64*)actual; - increment = sizeof(UNITY_INT64); - break; -#endif - - default: /* default is length 4 bytes */ - case 4: - expect_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)expected; - actual_val = *(UNITY_PTR_ATTRIBUTE const UNITY_INT32*)actual; - increment = sizeof(UNITY_INT32); - length = 4; - break; - } - - if ((style & UNITY_DISPLAY_RANGE_INT) == UNITY_DISPLAY_RANGE_INT) - { - if (actual_val > expect_val) - { - Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); - } - else - { - Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); - } - } - else - { - if ((UNITY_UINT)actual_val > (UNITY_UINT)expect_val) - { - Unity.CurrentTestFailed = (((UNITY_UINT)actual_val - (UNITY_UINT)expect_val) > delta); - } - else - { - Unity.CurrentTestFailed = (((UNITY_UINT)expect_val - (UNITY_UINT)actual_val) > delta); - } - } - - if (Unity.CurrentTestFailed) - { - if ((style & UNITY_DISPLAY_RANGE_UINT) && (length < (UNITY_INT_WIDTH / 8))) - { /* For UINT, remove sign extension (padding 1's) from signed type casts above */ - UNITY_INT mask = 1; - mask = (mask << 8 * length) - 1; - expect_val &= mask; - actual_val &= mask; - } - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrDelta); - UnityPrintNumberByStyle((UNITY_INT)delta, style); - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(expect_val, style); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(actual_val, style); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - /* Walk through array by incrementing the pointers */ - if (flags == UNITY_ARRAY_TO_ARRAY) - { - expected = (UNITY_INTERNAL_PTR)((const char*)expected + increment); - } - actual = (UNITY_INTERNAL_PTR)((const char*)actual + increment); - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualString(const char* expected, - const char* actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - UNITY_UINT32 i; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if both pointers not null compare the strings */ - if (expected && actual) - { - for (i = 0; expected[i] || actual[i]; i++) - { - if (expected[i] != actual[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrintExpectedAndActualStrings(expected, actual); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualStringLen(const char* expected, - const char* actual, - const UNITY_UINT32 length, - const char* msg, - const UNITY_LINE_TYPE lineNumber) -{ - UNITY_UINT32 i; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if both pointers not null compare the strings */ - if (expected && actual) - { - for (i = 0; (i < length) && (expected[i] || actual[i]); i++) - { - if (expected[i] != actual[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expected != actual) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrintExpectedAndActualStringsLen(expected, actual, length); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } -} - -/*-----------------------------------------------*/ -void UnityAssertEqualStringArray(UNITY_INTERNAL_PTR expected, - const char** actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_UINT32 i = 0; - UNITY_UINT32 j = 0; - const char* expd = NULL; - const char* act = NULL; - - RETURN_IF_FAIL_OR_IGNORE; - - /* if no elements, it's an error */ - if (num_elements == 0) - { - UnityPrintPointlessAndBail(); - } - - if ((const void*)expected == (const void*)actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull((UNITY_INTERNAL_PTR)expected, (UNITY_INTERNAL_PTR)actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - if (flags != UNITY_ARRAY_TO_ARRAY) - { - expd = (const char*)expected; - } - - do - { - act = actual[j]; - if (flags == UNITY_ARRAY_TO_ARRAY) - { - expd = ((const char* const*)expected)[j]; - } - - /* if both pointers not null compare the strings */ - if (expd && act) - { - for (i = 0; expd[i] || act[i]; i++) - { - if (expd[i] != act[i]) - { - Unity.CurrentTestFailed = 1; - break; - } - } - } - else - { /* handle case of one pointers being null (if both null, test should pass) */ - if (expd != act) - { - Unity.CurrentTestFailed = 1; - } - } - - if (Unity.CurrentTestFailed) - { - UnityTestResultsFailBegin(lineNumber); - if (num_elements > 1) - { - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(j); - } - UnityPrintExpectedAndActualStrings(expd, act); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - } while (++j < num_elements); -} - -/*-----------------------------------------------*/ -void UnityAssertEqualMemory(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 length, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags) -{ - UNITY_PTR_ATTRIBUTE const unsigned char* ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; - UNITY_PTR_ATTRIBUTE const unsigned char* ptr_act = (UNITY_PTR_ATTRIBUTE const unsigned char*)actual; - UNITY_UINT32 elements = num_elements; - UNITY_UINT32 bytes; - - RETURN_IF_FAIL_OR_IGNORE; - - if ((elements == 0) || (length == 0)) - { - UnityPrintPointlessAndBail(); - } - - if (expected == actual) - { - return; /* Both are NULL or same pointer */ - } - - if (UnityIsOneArrayNull(expected, actual, lineNumber, msg)) - { - UNITY_FAIL_AND_BAIL; - } - - while (elements--) - { - bytes = length; - while (bytes--) - { - if (*ptr_exp != *ptr_act) - { - UnityTestResultsFailBegin(lineNumber); - UnityPrint(UnityStrMemory); - if (num_elements > 1) - { - UnityPrint(UnityStrElement); - UnityPrintNumberUnsigned(num_elements - elements - 1); - } - UnityPrint(UnityStrByte); - UnityPrintNumberUnsigned(length - bytes - 1); - UnityPrint(UnityStrExpected); - UnityPrintNumberByStyle(*ptr_exp, UNITY_DISPLAY_STYLE_HEX8); - UnityPrint(UnityStrWas); - UnityPrintNumberByStyle(*ptr_act, UNITY_DISPLAY_STYLE_HEX8); - UnityAddMsgIfSpecified(msg); - UNITY_FAIL_AND_BAIL; - } - ptr_exp++; - ptr_act++; - } - if (flags == UNITY_ARRAY_TO_VAL) - { - ptr_exp = (UNITY_PTR_ATTRIBUTE const unsigned char*)expected; - } - } -} - -/*-----------------------------------------------*/ - -static union -{ - UNITY_INT8 i8; - UNITY_INT16 i16; - UNITY_INT32 i32; -#ifdef UNITY_SUPPORT_64 - UNITY_INT64 i64; -#endif -#ifndef UNITY_EXCLUDE_FLOAT - float f; -#endif -#ifndef UNITY_EXCLUDE_DOUBLE - double d; -#endif -} UnityQuickCompare; - -UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size) -{ - switch(size) - { - case 1: - UnityQuickCompare.i8 = (UNITY_INT8)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i8); - - case 2: - UnityQuickCompare.i16 = (UNITY_INT16)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i16); - -#ifdef UNITY_SUPPORT_64 - case 8: - UnityQuickCompare.i64 = (UNITY_INT64)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i64); -#endif - - default: /* 4 bytes */ - UnityQuickCompare.i32 = (UNITY_INT32)num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.i32); - } -} - -#ifndef UNITY_EXCLUDE_FLOAT -/*-----------------------------------------------*/ -UNITY_INTERNAL_PTR UnityFloatToPtr(const float num) -{ - UnityQuickCompare.f = num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.f); -} -#endif - -#ifndef UNITY_EXCLUDE_DOUBLE -/*-----------------------------------------------*/ -UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num) -{ - UnityQuickCompare.d = num; - return (UNITY_INTERNAL_PTR)(&UnityQuickCompare.d); -} -#endif - -/*----------------------------------------------- - * printf helper function - *-----------------------------------------------*/ -#ifdef UNITY_INCLUDE_PRINT_FORMATTED -static void UnityPrintFVA(const char* format, va_list va) -{ - const char* pch = format; - if (pch != NULL) - { - while (*pch) - { - /* format identification character */ - if (*pch == '%') - { - pch++; - - if (pch != NULL) - { - switch (*pch) - { - case 'd': - case 'i': - { - const int number = va_arg(va, int); - UnityPrintNumber((UNITY_INT)number); - break; - } -#ifndef UNITY_EXCLUDE_FLOAT_PRINT - case 'f': - case 'g': - { - const double number = va_arg(va, double); - UnityPrintFloat((UNITY_DOUBLE)number); - break; - } -#endif - case 'u': - { - const unsigned int number = va_arg(va, unsigned int); - UnityPrintNumberUnsigned((UNITY_UINT)number); - break; - } - case 'b': - { - const unsigned int number = va_arg(va, unsigned int); - const UNITY_UINT mask = (UNITY_UINT)0 - (UNITY_UINT)1; - UNITY_OUTPUT_CHAR('0'); - UNITY_OUTPUT_CHAR('b'); - UnityPrintMask(mask, (UNITY_UINT)number); - break; - } - case 'x': - case 'X': - case 'p': - { - const unsigned int number = va_arg(va, unsigned int); - UNITY_OUTPUT_CHAR('0'); - UNITY_OUTPUT_CHAR('x'); - UnityPrintNumberHex((UNITY_UINT)number, 8); - break; - } - case 'c': - { - const int ch = va_arg(va, int); - UnityPrintChar((const char *)&ch); - break; - } - case 's': - { - const char * string = va_arg(va, const char *); - UnityPrint(string); - break; - } - case '%': - { - UnityPrintChar(pch); - break; - } - default: - { - /* print the unknown format character */ - UNITY_OUTPUT_CHAR('%'); - UnityPrintChar(pch); - break; - } - } - } - } -#ifdef UNITY_OUTPUT_COLOR - /* print ANSI escape code */ - else if ((*pch == 27) && (*(pch + 1) == '[')) - { - pch += UnityPrintAnsiEscapeString(pch); - continue; - } -#endif - else if (*pch == '\n') - { - UNITY_PRINT_EOL(); - } - else - { - UnityPrintChar(pch); - } - - pch++; - } - } -} - -void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...) -{ - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint("INFO"); - if(format != NULL) - { - UnityPrint(": "); - va_list va; - va_start(va, format); - UnityPrintFVA(format, va); - va_end(va); - } - UNITY_PRINT_EOL(); -} -#endif /* ! UNITY_INCLUDE_PRINT_FORMATTED */ - - -/*----------------------------------------------- - * Control Functions - *-----------------------------------------------*/ - -/*-----------------------------------------------*/ -void UnityFail(const char* msg, const UNITY_LINE_TYPE line) -{ - RETURN_IF_FAIL_OR_IGNORE; - - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrFail); - if (msg != NULL) - { - UNITY_OUTPUT_CHAR(':'); - -#ifdef UNITY_PRINT_TEST_CONTEXT - UNITY_PRINT_TEST_CONTEXT(); -#endif -#ifndef UNITY_EXCLUDE_DETAILS - if (Unity.CurrentDetail1) - { - UnityPrint(UnityStrDetail1Name); - UnityPrint(Unity.CurrentDetail1); - if (Unity.CurrentDetail2) - { - UnityPrint(UnityStrDetail2Name); - UnityPrint(Unity.CurrentDetail2); - } - UnityPrint(UnityStrSpacer); - } -#endif - if (msg[0] != ' ') - { - UNITY_OUTPUT_CHAR(' '); - } - UnityPrint(msg); - } - - UNITY_FAIL_AND_BAIL; -} - -/*-----------------------------------------------*/ -void UnityIgnore(const char* msg, const UNITY_LINE_TYPE line) -{ - RETURN_IF_FAIL_OR_IGNORE; - - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint(UnityStrIgnore); - if (msg != NULL) - { - UNITY_OUTPUT_CHAR(':'); - UNITY_OUTPUT_CHAR(' '); - UnityPrint(msg); - } - UNITY_IGNORE_AND_BAIL; -} - -/*-----------------------------------------------*/ -void UnityMessage(const char* msg, const UNITY_LINE_TYPE line) -{ - UnityTestResultsBegin(Unity.TestFile, line); - UnityPrint("INFO"); - if (msg != NULL) - { - UNITY_OUTPUT_CHAR(':'); - UNITY_OUTPUT_CHAR(' '); - UnityPrint(msg); - } - UNITY_PRINT_EOL(); -} - -/*-----------------------------------------------*/ -/* If we have not defined our own test runner, then include our default test runner to make life easier */ -#ifndef UNITY_SKIP_DEFAULT_RUNNER -void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum) -{ - Unity.CurrentTestName = FuncName; - Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)FuncLineNum; - Unity.NumberOfTests++; - UNITY_CLR_DETAILS(); - UNITY_EXEC_TIME_START(); - if (TEST_PROTECT()) - { - setUp(); - Func(); - } - if (TEST_PROTECT()) - { - tearDown(); - } - UNITY_EXEC_TIME_STOP(); - UnityConcludeTest(); -} -#endif - -/*-----------------------------------------------*/ -void UnitySetTestFile(const char* filename) -{ - Unity.TestFile = filename; -} - -/*-----------------------------------------------*/ -void UnityBegin(const char* filename) -{ - Unity.TestFile = filename; - Unity.CurrentTestName = NULL; - Unity.CurrentTestLineNumber = 0; - Unity.NumberOfTests = 0; - Unity.TestFailures = 0; - Unity.TestIgnores = 0; - Unity.CurrentTestFailed = 0; - Unity.CurrentTestIgnored = 0; - - UNITY_CLR_DETAILS(); - UNITY_OUTPUT_START(); -} - -/*-----------------------------------------------*/ -int UnityEnd(void) -{ - UNITY_PRINT_EOL(); - UnityPrint(UnityStrBreaker); - UNITY_PRINT_EOL(); - UnityPrintNumber((UNITY_INT)(Unity.NumberOfTests)); - UnityPrint(UnityStrResultsTests); - UnityPrintNumber((UNITY_INT)(Unity.TestFailures)); - UnityPrint(UnityStrResultsFailures); - UnityPrintNumber((UNITY_INT)(Unity.TestIgnores)); - UnityPrint(UnityStrResultsIgnored); - UNITY_PRINT_EOL(); - if (Unity.TestFailures == 0U) - { - UnityPrint(UnityStrOk); - } - else - { - UnityPrint(UnityStrFail); -#ifdef UNITY_DIFFERENTIATE_FINAL_FAIL - UNITY_OUTPUT_CHAR('E'); UNITY_OUTPUT_CHAR('D'); -#endif - } - UNITY_PRINT_EOL(); - UNITY_FLUSH_CALL(); - UNITY_OUTPUT_COMPLETE(); - return (int)(Unity.TestFailures); -} - -/*----------------------------------------------- - * Command Line Argument Support - *-----------------------------------------------*/ -#ifdef UNITY_USE_COMMAND_LINE_ARGS - -char* UnityOptionIncludeNamed = NULL; -char* UnityOptionExcludeNamed = NULL; -int UnityVerbosity = 1; - -/*-----------------------------------------------*/ -int UnityParseOptions(int argc, char** argv) -{ - int i; - UnityOptionIncludeNamed = NULL; - UnityOptionExcludeNamed = NULL; - - for (i = 1; i < argc; i++) - { - if (argv[i][0] == '-') - { - switch (argv[i][1]) - { - case 'l': /* list tests */ - return -1; - case 'n': /* include tests with name including this string */ - case 'f': /* an alias for -n */ - if (argv[i][2] == '=') - { - UnityOptionIncludeNamed = &argv[i][3]; - } - else if (++i < argc) - { - UnityOptionIncludeNamed = argv[i]; - } - else - { - UnityPrint("ERROR: No Test String to Include Matches For"); - UNITY_PRINT_EOL(); - return 1; - } - break; - case 'q': /* quiet */ - UnityVerbosity = 0; - break; - case 'v': /* verbose */ - UnityVerbosity = 2; - break; - case 'x': /* exclude tests with name including this string */ - if (argv[i][2] == '=') - { - UnityOptionExcludeNamed = &argv[i][3]; - } - else if (++i < argc) - { - UnityOptionExcludeNamed = argv[i]; - } - else - { - UnityPrint("ERROR: No Test String to Exclude Matches For"); - UNITY_PRINT_EOL(); - return 1; - } - break; - default: - UnityPrint("ERROR: Unknown Option "); - UNITY_OUTPUT_CHAR(argv[i][1]); - UNITY_PRINT_EOL(); - return 1; - } - } - } - - return 0; -} - -/*-----------------------------------------------*/ -int IsStringInBiggerString(const char* longstring, const char* shortstring) -{ - const char* lptr = longstring; - const char* sptr = shortstring; - const char* lnext = lptr; - - if (*sptr == '*') - { - return 1; - } - - while (*lptr) - { - lnext = lptr + 1; - - /* If they current bytes match, go on to the next bytes */ - while (*lptr && *sptr && (*lptr == *sptr)) - { - lptr++; - sptr++; - - /* We're done if we match the entire string or up to a wildcard */ - if (*sptr == '*') - return 1; - if (*sptr == ',') - return 1; - if (*sptr == '"') - return 1; - if (*sptr == '\'') - return 1; - if (*sptr == ':') - return 2; - if (*sptr == 0) - return 1; - } - - /* Otherwise we start in the long pointer 1 character further and try again */ - lptr = lnext; - sptr = shortstring; - } - - return 0; -} - -/*-----------------------------------------------*/ -int UnityStringArgumentMatches(const char* str) -{ - int retval; - const char* ptr1; - const char* ptr2; - const char* ptrf; - - /* Go through the options and get the substrings for matching one at a time */ - ptr1 = str; - while (ptr1[0] != 0) - { - if ((ptr1[0] == '"') || (ptr1[0] == '\'')) - { - ptr1++; - } - - /* look for the start of the next partial */ - ptr2 = ptr1; - ptrf = 0; - do - { - ptr2++; - if ((ptr2[0] == ':') && (ptr2[1] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')) - { - ptrf = &ptr2[1]; - } - } while ((ptr2[0] != 0) && (ptr2[0] != '\'') && (ptr2[0] != '"') && (ptr2[0] != ',')); - - while ((ptr2[0] != 0) && ((ptr2[0] == ':') || (ptr2[0] == '\'') || (ptr2[0] == '"') || (ptr2[0] == ','))) - { - ptr2++; - } - - /* done if complete filename match */ - retval = IsStringInBiggerString(Unity.TestFile, ptr1); - if (retval == 1) - { - return retval; - } - - /* done if testname match after filename partial match */ - if ((retval == 2) && (ptrf != 0)) - { - if (IsStringInBiggerString(Unity.CurrentTestName, ptrf)) - { - return 1; - } - } - - /* done if complete testname match */ - if (IsStringInBiggerString(Unity.CurrentTestName, ptr1) == 1) - { - return 1; - } - - ptr1 = ptr2; - } - - /* we couldn't find a match for any substrings */ - return 0; -} - -/*-----------------------------------------------*/ -int UnityTestMatches(void) -{ - /* Check if this test name matches the included test pattern */ - int retval; - if (UnityOptionIncludeNamed) - { - retval = UnityStringArgumentMatches(UnityOptionIncludeNamed); - } - else - { - retval = 1; - } - - /* Check if this test name matches the excluded test pattern */ - if (UnityOptionExcludeNamed) - { - if (UnityStringArgumentMatches(UnityOptionExcludeNamed)) - { - retval = 0; - } - } - - return retval; -} - -#endif /* UNITY_USE_COMMAND_LINE_ARGS */ -/*-----------------------------------------------*/ diff --git a/taskchampion/integration-tests/src/bindings_tests/unity/unity.h b/taskchampion/integration-tests/src/bindings_tests/unity/unity.h deleted file mode 100644 index 14225a354..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/unity/unity.h +++ /dev/null @@ -1,661 +0,0 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ - -#ifndef UNITY_FRAMEWORK_H -#define UNITY_FRAMEWORK_H -#define UNITY - -#define UNITY_VERSION_MAJOR 2 -#define UNITY_VERSION_MINOR 5 -#define UNITY_VERSION_BUILD 4 -#define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD) - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include "unity_internals.h" - -/*------------------------------------------------------- - * Test Setup / Teardown - *-------------------------------------------------------*/ - -/* These functions are intended to be called before and after each test. - * If using unity directly, these will need to be provided for each test - * executable built. If you are using the test runner generator and/or - * Ceedling, these are optional. */ -void setUp(void); -void tearDown(void); - -/* These functions are intended to be called at the beginning and end of an - * entire test suite. suiteTearDown() is passed the number of tests that - * failed, and its return value becomes the exit code of main(). If using - * Unity directly, you're in charge of calling these if they are desired. - * If using Ceedling or the test runner generator, these will be called - * automatically if they exist. */ -void suiteSetUp(void); -int suiteTearDown(int num_failures); - -/*------------------------------------------------------- - * Test Reset and Verify - *-------------------------------------------------------*/ - -/* These functions are intended to be called before during tests in order - * to support complex test loops, etc. Both are NOT built into Unity. Instead - * the test runner generator will create them. resetTest will run teardown and - * setup again, verifying any end-of-test needs between. verifyTest will only - * run the verification. */ -void resetTest(void); -void verifyTest(void); - -/*------------------------------------------------------- - * Configuration Options - *------------------------------------------------------- - * All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above. - - * Integers/longs/pointers - * - Unity attempts to automatically discover your integer sizes - * - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in - * - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in - * - If you cannot use the automatic methods above, you can force Unity by using these options: - * - define UNITY_SUPPORT_64 - * - set UNITY_INT_WIDTH - * - set UNITY_LONG_WIDTH - * - set UNITY_POINTER_WIDTH - - * Floats - * - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons - * - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT - * - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats - * - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons - * - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default) - * - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE - * - define UNITY_DOUBLE_TYPE to specify something other than double - * - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors - - * Output - * - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired - * - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure - - * Optimization - * - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge - * - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests. - - * Test Cases - * - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script - - * Parameterized Tests - * - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing - - * Tests with Arguments - * - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity - - *------------------------------------------------------- - * Basic Fail and Ignore - *-------------------------------------------------------*/ - -#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message)) -#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL) -#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message)) -#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL) -#define TEST_MESSAGE(message) UnityMessage((message), __LINE__) -#define TEST_ONLY() -#ifdef UNITY_INCLUDE_PRINT_FORMATTED -#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), __VA_ARGS__) -#endif - -/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails. - * This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */ -#define TEST_PASS() TEST_ABORT() -#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0) - -/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out - * which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */ -#define TEST_FILE(a) - -/*------------------------------------------------------- - * Test Asserts (simple) - *-------------------------------------------------------*/ - -/* Boolean */ -#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE") -#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE") -#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE") -#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE") -#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL") -#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL") -#define TEST_ASSERT_EMPTY(pointer) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, " Expected Empty") -#define TEST_ASSERT_NOT_EMPTY(pointer) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, " Expected Non-Empty") - -/* Integers (of all sizes) */ -#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_size_t(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_CHAR(expected, actual) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(-1), (actual), __LINE__, NULL) -#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(0), (actual), __LINE__, NULL) -#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(-1), (actual), __LINE__, NULL) -#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(0), (actual), __LINE__, NULL) - -/* Integer Not Equal To (of all sizes) */ -#define TEST_ASSERT_NOT_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) - -/* Integer Greater Than/ Less Than (of all sizes) */ -#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) - -#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL) -#define TEST_ASSERT_LESS_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL) - -/* Integer Ranges (of all sizes) */ -#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_size_t_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_CHAR_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, NULL) - -/* Integer Array Ranges (of all sizes) */ -#define TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_size_t_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_HEX_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) -#define TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL) - - -/* Structs and Strings */ -#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL) - -/* Arrays */ -#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_size_t_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) - -/* Arrays Compared To Single Value */ -#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_size_t(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, NULL) - -/* Floating Point (If Enabled) */ -#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL) - -/* Double (If Enabled) */ -#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL) -#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL) - -/* Shorthand */ -#ifdef UNITY_SHORTHAND_AS_OLD -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") -#endif -#ifdef UNITY_SHORTHAND_AS_INT -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif -#ifdef UNITY_SHORTHAND_AS_MEM -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, NULL) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif -#ifdef UNITY_SHORTHAND_AS_RAW -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, " Expected Equal") -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal") -#endif -#ifdef UNITY_SHORTHAND_AS_NONE -#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif - -/*------------------------------------------------------- - * Test Asserts (with additional messages) - *-------------------------------------------------------*/ - -/* Boolean */ -#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) -#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message)) -#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) -#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message)) -#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message)) -#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message)) -#define TEST_ASSERT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, (message)) -#define TEST_ASSERT_NOT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, (message)) - -/* Integers (of all sizes) */ -#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_size_t_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) -#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message)) -#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message)) -#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_CHAR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, (message)) - -/* Integer Not Equal To (of all sizes) */ -#define TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) - - -/* Integer Greater Than/ Less Than (of all sizes) */ -#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) - -#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message)) -#define TEST_ASSERT_LESS_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message)) - -/* Integer Ranges (of all sizes) */ -#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_size_t_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_CHAR_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, (message)) - -/* Integer Array Ranges (of all sizes) */ -#define TEST_ASSERT_INT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_INT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_INT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_INT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_INT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_UINT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_UINT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_UINT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_UINT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_UINT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_size_t_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_HEX_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_HEX8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_HEX16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_HEX32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_HEX64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) -#define TEST_ASSERT_CHAR_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message)) - - -/* Structs and Strings */ -#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message)) - -/* Arrays */ -#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_size_t_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) - -/* Arrays Compared To Single Value*/ -#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_size_t_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_CHAR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, (message)) - -/* Floating Point (If Enabled) */ -#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message)) - -/* Double (If Enabled) */ -#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message)) -#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message)) - -/* Shorthand */ -#ifdef UNITY_SHORTHAND_AS_OLD -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message)) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message)) -#endif -#ifdef UNITY_SHORTHAND_AS_INT -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, message) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif -#ifdef UNITY_SHORTHAND_AS_MEM -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, message) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif -#ifdef UNITY_SHORTHAND_AS_RAW -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, message) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, message) -#endif -#ifdef UNITY_SHORTHAND_AS_NONE -#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand) -#endif - -/* end of UNITY_FRAMEWORK_H */ -#ifdef __cplusplus -} -#endif -#endif diff --git a/taskchampion/integration-tests/src/bindings_tests/unity/unity_internals.h b/taskchampion/integration-tests/src/bindings_tests/unity/unity_internals.h deleted file mode 100644 index d303e8fe7..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/unity/unity_internals.h +++ /dev/null @@ -1,1053 +0,0 @@ -/* ========================================== - Unity Project - A Test Framework for C - Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams - [Released under MIT License. Please refer to license.txt for details] -========================================== */ - -#ifndef UNITY_INTERNALS_H -#define UNITY_INTERNALS_H - -#ifdef UNITY_INCLUDE_CONFIG_H -#include "unity_config.h" -#endif - -#ifndef UNITY_EXCLUDE_SETJMP_H -#include -#endif - -#ifndef UNITY_EXCLUDE_MATH_H -#include -#endif - -#ifndef UNITY_EXCLUDE_STDDEF_H -#include -#endif - -#ifdef UNITY_INCLUDE_PRINT_FORMATTED -#include -#endif - -/* Unity Attempts to Auto-Detect Integer Types - * Attempt 1: UINT_MAX, ULONG_MAX in , or default to 32 bits - * Attempt 2: UINTPTR_MAX in , or default to same size as long - * The user may override any of these derived constants: - * UNITY_INT_WIDTH, UNITY_LONG_WIDTH, UNITY_POINTER_WIDTH */ -#ifndef UNITY_EXCLUDE_STDINT_H -#include -#endif - -#ifndef UNITY_EXCLUDE_LIMITS_H -#include -#endif - -#if defined(__GNUC__) || defined(__clang__) - #define UNITY_FUNCTION_ATTR(a) __attribute__((a)) -#else - #define UNITY_FUNCTION_ATTR(a) /* ignore */ -#endif - -#ifndef UNITY_NORETURN - #if defined(__cplusplus) - #if __cplusplus >= 201103L - #define UNITY_NORETURN [[ noreturn ]] - #endif - #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L - #include - #define UNITY_NORETURN noreturn - #endif -#endif -#ifndef UNITY_NORETURN - #define UNITY_NORETURN UNITY_FUNCTION_ATTR(noreturn) -#endif - -/*------------------------------------------------------- - * Guess Widths If Not Specified - *-------------------------------------------------------*/ - -/* Determine the size of an int, if not already specified. - * We cannot use sizeof(int), because it is not yet defined - * at this stage in the translation of the C program. - * Also sizeof(int) does return the size in addressable units on all platforms, - * which may not necessarily be the size in bytes. - * Therefore, infer it from UINT_MAX if possible. */ -#ifndef UNITY_INT_WIDTH - #ifdef UINT_MAX - #if (UINT_MAX == 0xFFFF) - #define UNITY_INT_WIDTH (16) - #elif (UINT_MAX == 0xFFFFFFFF) - #define UNITY_INT_WIDTH (32) - #elif (UINT_MAX == 0xFFFFFFFFFFFFFFFF) - #define UNITY_INT_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_INT_WIDTH (32) - #endif /* UINT_MAX */ -#endif - -/* Determine the size of a long, if not already specified. */ -#ifndef UNITY_LONG_WIDTH - #ifdef ULONG_MAX - #if (ULONG_MAX == 0xFFFF) - #define UNITY_LONG_WIDTH (16) - #elif (ULONG_MAX == 0xFFFFFFFF) - #define UNITY_LONG_WIDTH (32) - #elif (ULONG_MAX == 0xFFFFFFFFFFFFFFFF) - #define UNITY_LONG_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_LONG_WIDTH (32) - #endif /* ULONG_MAX */ -#endif - -/* Determine the size of a pointer, if not already specified. */ -#ifndef UNITY_POINTER_WIDTH - #ifdef UINTPTR_MAX - #if (UINTPTR_MAX <= 0xFFFF) - #define UNITY_POINTER_WIDTH (16) - #elif (UINTPTR_MAX <= 0xFFFFFFFF) - #define UNITY_POINTER_WIDTH (32) - #elif (UINTPTR_MAX <= 0xFFFFFFFFFFFFFFFF) - #define UNITY_POINTER_WIDTH (64) - #endif - #else /* Set to default */ - #define UNITY_POINTER_WIDTH UNITY_LONG_WIDTH - #endif /* UINTPTR_MAX */ -#endif - -/*------------------------------------------------------- - * Int Support (Define types based on detected sizes) - *-------------------------------------------------------*/ - -#if (UNITY_INT_WIDTH == 32) - typedef unsigned char UNITY_UINT8; - typedef unsigned short UNITY_UINT16; - typedef unsigned int UNITY_UINT32; - typedef signed char UNITY_INT8; - typedef signed short UNITY_INT16; - typedef signed int UNITY_INT32; -#elif (UNITY_INT_WIDTH == 16) - typedef unsigned char UNITY_UINT8; - typedef unsigned int UNITY_UINT16; - typedef unsigned long UNITY_UINT32; - typedef signed char UNITY_INT8; - typedef signed int UNITY_INT16; - typedef signed long UNITY_INT32; -#else - #error Invalid UNITY_INT_WIDTH specified! (16 or 32 are supported) -#endif - -/*------------------------------------------------------- - * 64-bit Support - *-------------------------------------------------------*/ - -/* Auto-detect 64 Bit Support */ -#ifndef UNITY_SUPPORT_64 - #if UNITY_LONG_WIDTH == 64 || UNITY_POINTER_WIDTH == 64 - #define UNITY_SUPPORT_64 - #endif -#endif - -/* 64-Bit Support Dependent Configuration */ -#ifndef UNITY_SUPPORT_64 - /* No 64-bit Support */ - typedef UNITY_UINT32 UNITY_UINT; - typedef UNITY_INT32 UNITY_INT; - #define UNITY_MAX_NIBBLES (8) /* Maximum number of nibbles in a UNITY_(U)INT */ -#else - /* 64-bit Support */ - #if (UNITY_LONG_WIDTH == 32) - typedef unsigned long long UNITY_UINT64; - typedef signed long long UNITY_INT64; - #elif (UNITY_LONG_WIDTH == 64) - typedef unsigned long UNITY_UINT64; - typedef signed long UNITY_INT64; - #else - #error Invalid UNITY_LONG_WIDTH specified! (32 or 64 are supported) - #endif - typedef UNITY_UINT64 UNITY_UINT; - typedef UNITY_INT64 UNITY_INT; - #define UNITY_MAX_NIBBLES (16) /* Maximum number of nibbles in a UNITY_(U)INT */ -#endif - -/*------------------------------------------------------- - * Pointer Support - *-------------------------------------------------------*/ - -#if (UNITY_POINTER_WIDTH == 32) - #define UNITY_PTR_TO_INT UNITY_INT32 - #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX32 -#elif (UNITY_POINTER_WIDTH == 64) - #define UNITY_PTR_TO_INT UNITY_INT64 - #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX64 -#elif (UNITY_POINTER_WIDTH == 16) - #define UNITY_PTR_TO_INT UNITY_INT16 - #define UNITY_DISPLAY_STYLE_POINTER UNITY_DISPLAY_STYLE_HEX16 -#else - #error Invalid UNITY_POINTER_WIDTH specified! (16, 32 or 64 are supported) -#endif - -#ifndef UNITY_PTR_ATTRIBUTE - #define UNITY_PTR_ATTRIBUTE -#endif - -#ifndef UNITY_INTERNAL_PTR - #define UNITY_INTERNAL_PTR UNITY_PTR_ATTRIBUTE const void* -#endif - -/*------------------------------------------------------- - * Float Support - *-------------------------------------------------------*/ - -#ifdef UNITY_EXCLUDE_FLOAT - -/* No Floating Point Support */ -#ifndef UNITY_EXCLUDE_DOUBLE -#define UNITY_EXCLUDE_DOUBLE /* Remove double when excluding float support */ -#endif -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -#define UNITY_EXCLUDE_FLOAT_PRINT -#endif - -#else - -/* Floating Point Support */ -#ifndef UNITY_FLOAT_PRECISION -#define UNITY_FLOAT_PRECISION (0.00001f) -#endif -#ifndef UNITY_FLOAT_TYPE -#define UNITY_FLOAT_TYPE float -#endif -typedef UNITY_FLOAT_TYPE UNITY_FLOAT; - -/* isinf & isnan macros should be provided by math.h */ -#ifndef isinf -/* The value of Inf - Inf is NaN */ -#define isinf(n) (isnan((n) - (n)) && !isnan(n)) -#endif - -#ifndef isnan -/* NaN is the only floating point value that does NOT equal itself. - * Therefore if n != n, then it is NaN. */ -#define isnan(n) ((n != n) ? 1 : 0) -#endif - -#endif - -/*------------------------------------------------------- - * Double Float Support - *-------------------------------------------------------*/ - -/* unlike float, we DON'T include by default */ -#if defined(UNITY_EXCLUDE_DOUBLE) || !defined(UNITY_INCLUDE_DOUBLE) - - /* No Floating Point Support */ - #ifndef UNITY_EXCLUDE_DOUBLE - #define UNITY_EXCLUDE_DOUBLE - #else - #undef UNITY_INCLUDE_DOUBLE - #endif - - #ifndef UNITY_EXCLUDE_FLOAT - #ifndef UNITY_DOUBLE_TYPE - #define UNITY_DOUBLE_TYPE double - #endif - typedef UNITY_FLOAT UNITY_DOUBLE; - /* For parameter in UnityPrintFloat(UNITY_DOUBLE), which aliases to double or float */ - #endif - -#else - - /* Double Floating Point Support */ - #ifndef UNITY_DOUBLE_PRECISION - #define UNITY_DOUBLE_PRECISION (1e-12) - #endif - - #ifndef UNITY_DOUBLE_TYPE - #define UNITY_DOUBLE_TYPE double - #endif - typedef UNITY_DOUBLE_TYPE UNITY_DOUBLE; - -#endif - -/*------------------------------------------------------- - * Output Method: stdout (DEFAULT) - *-------------------------------------------------------*/ -#ifndef UNITY_OUTPUT_CHAR - /* Default to using putchar, which is defined in stdio.h */ - #include - #define UNITY_OUTPUT_CHAR(a) (void)putchar(a) -#else - /* If defined as something else, make sure we declare it here so it's ready for use */ - #ifdef UNITY_OUTPUT_CHAR_HEADER_DECLARATION - extern void UNITY_OUTPUT_CHAR_HEADER_DECLARATION; - #endif -#endif - -#ifndef UNITY_OUTPUT_FLUSH - #ifdef UNITY_USE_FLUSH_STDOUT - /* We want to use the stdout flush utility */ - #include - #define UNITY_OUTPUT_FLUSH() (void)fflush(stdout) - #else - /* We've specified nothing, therefore flush should just be ignored */ - #define UNITY_OUTPUT_FLUSH() (void)0 - #endif -#else - /* If defined as something else, make sure we declare it here so it's ready for use */ - #ifdef UNITY_OUTPUT_FLUSH_HEADER_DECLARATION - extern void UNITY_OUTPUT_FLUSH_HEADER_DECLARATION; - #endif -#endif - -#ifndef UNITY_OUTPUT_FLUSH -#define UNITY_FLUSH_CALL() -#else -#define UNITY_FLUSH_CALL() UNITY_OUTPUT_FLUSH() -#endif - -#ifndef UNITY_PRINT_EOL -#define UNITY_PRINT_EOL() UNITY_OUTPUT_CHAR('\n') -#endif - -#ifndef UNITY_OUTPUT_START -#define UNITY_OUTPUT_START() -#endif - -#ifndef UNITY_OUTPUT_COMPLETE -#define UNITY_OUTPUT_COMPLETE() -#endif - -#ifdef UNITY_INCLUDE_EXEC_TIME - #if !defined(UNITY_EXEC_TIME_START) && \ - !defined(UNITY_EXEC_TIME_STOP) && \ - !defined(UNITY_PRINT_EXEC_TIME) && \ - !defined(UNITY_TIME_TYPE) - /* If none any of these macros are defined then try to provide a default implementation */ - - #if defined(UNITY_CLOCK_MS) - /* This is a simple way to get a default implementation on platforms that support getting a millisecond counter */ - #define UNITY_TIME_TYPE UNITY_UINT - #define UNITY_EXEC_TIME_START() Unity.CurrentTestStartTime = UNITY_CLOCK_MS() - #define UNITY_EXEC_TIME_STOP() Unity.CurrentTestStopTime = UNITY_CLOCK_MS() - #define UNITY_PRINT_EXEC_TIME() { \ - UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ - UnityPrint(" ("); \ - UnityPrintNumberUnsigned(execTimeMs); \ - UnityPrint(" ms)"); \ - } - #elif defined(_WIN32) - #include - #define UNITY_TIME_TYPE clock_t - #define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC) - #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) - #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) - #define UNITY_PRINT_EXEC_TIME() { \ - UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \ - UnityPrint(" ("); \ - UnityPrintNumberUnsigned(execTimeMs); \ - UnityPrint(" ms)"); \ - } - #elif defined(__unix__) || defined(__APPLE__) - #include - #define UNITY_TIME_TYPE struct timespec - #define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t) - #define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime) - #define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime) - #define UNITY_PRINT_EXEC_TIME() { \ - UNITY_UINT execTimeMs = ((Unity.CurrentTestStopTime.tv_sec - Unity.CurrentTestStartTime.tv_sec) * 1000L); \ - execTimeMs += ((Unity.CurrentTestStopTime.tv_nsec - Unity.CurrentTestStartTime.tv_nsec) / 1000000L); \ - UnityPrint(" ("); \ - UnityPrintNumberUnsigned(execTimeMs); \ - UnityPrint(" ms)"); \ - } - #endif - #endif -#endif - -#ifndef UNITY_EXEC_TIME_START -#define UNITY_EXEC_TIME_START() do { /* nothing*/ } while (0) -#endif - -#ifndef UNITY_EXEC_TIME_STOP -#define UNITY_EXEC_TIME_STOP() do { /* nothing*/ } while (0) -#endif - -#ifndef UNITY_TIME_TYPE -#define UNITY_TIME_TYPE UNITY_UINT -#endif - -#ifndef UNITY_PRINT_EXEC_TIME -#define UNITY_PRINT_EXEC_TIME() do { /* nothing*/ } while (0) -#endif - -/*------------------------------------------------------- - * Footprint - *-------------------------------------------------------*/ - -#ifndef UNITY_LINE_TYPE -#define UNITY_LINE_TYPE UNITY_UINT -#endif - -#ifndef UNITY_COUNTER_TYPE -#define UNITY_COUNTER_TYPE UNITY_UINT -#endif - -/*------------------------------------------------------- - * Internal Structs Needed - *-------------------------------------------------------*/ - -typedef void (*UnityTestFunction)(void); - -#define UNITY_DISPLAY_RANGE_INT (0x10) -#define UNITY_DISPLAY_RANGE_UINT (0x20) -#define UNITY_DISPLAY_RANGE_HEX (0x40) -#define UNITY_DISPLAY_RANGE_CHAR (0x80) - -typedef enum -{ - UNITY_DISPLAY_STYLE_INT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT8 = 1 + UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT16 = 2 + UNITY_DISPLAY_RANGE_INT, - UNITY_DISPLAY_STYLE_INT32 = 4 + UNITY_DISPLAY_RANGE_INT, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_INT64 = 8 + UNITY_DISPLAY_RANGE_INT, -#endif - - UNITY_DISPLAY_STYLE_UINT = (UNITY_INT_WIDTH / 8) + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT8 = 1 + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT16 = 2 + UNITY_DISPLAY_RANGE_UINT, - UNITY_DISPLAY_STYLE_UINT32 = 4 + UNITY_DISPLAY_RANGE_UINT, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_UINT64 = 8 + UNITY_DISPLAY_RANGE_UINT, -#endif - - UNITY_DISPLAY_STYLE_HEX8 = 1 + UNITY_DISPLAY_RANGE_HEX, - UNITY_DISPLAY_STYLE_HEX16 = 2 + UNITY_DISPLAY_RANGE_HEX, - UNITY_DISPLAY_STYLE_HEX32 = 4 + UNITY_DISPLAY_RANGE_HEX, -#ifdef UNITY_SUPPORT_64 - UNITY_DISPLAY_STYLE_HEX64 = 8 + UNITY_DISPLAY_RANGE_HEX, -#endif - - UNITY_DISPLAY_STYLE_CHAR = 1 + UNITY_DISPLAY_RANGE_CHAR + UNITY_DISPLAY_RANGE_INT, - - UNITY_DISPLAY_STYLE_UNKNOWN -} UNITY_DISPLAY_STYLE_T; - -typedef enum -{ - UNITY_WITHIN = 0x0, - UNITY_EQUAL_TO = 0x1, - UNITY_GREATER_THAN = 0x2, - UNITY_GREATER_OR_EQUAL = 0x2 + UNITY_EQUAL_TO, - UNITY_SMALLER_THAN = 0x4, - UNITY_SMALLER_OR_EQUAL = 0x4 + UNITY_EQUAL_TO, - UNITY_NOT_EQUAL = 0x0, - UNITY_UNKNOWN -} UNITY_COMPARISON_T; - -#ifndef UNITY_EXCLUDE_FLOAT -typedef enum UNITY_FLOAT_TRAIT -{ - UNITY_FLOAT_IS_NOT_INF = 0, - UNITY_FLOAT_IS_INF, - UNITY_FLOAT_IS_NOT_NEG_INF, - UNITY_FLOAT_IS_NEG_INF, - UNITY_FLOAT_IS_NOT_NAN, - UNITY_FLOAT_IS_NAN, - UNITY_FLOAT_IS_NOT_DET, - UNITY_FLOAT_IS_DET, - UNITY_FLOAT_INVALID_TRAIT -} UNITY_FLOAT_TRAIT_T; -#endif - -typedef enum -{ - UNITY_ARRAY_TO_VAL = 0, - UNITY_ARRAY_TO_ARRAY, - UNITY_ARRAY_UNKNOWN -} UNITY_FLAGS_T; - -struct UNITY_STORAGE_T -{ - const char* TestFile; - const char* CurrentTestName; -#ifndef UNITY_EXCLUDE_DETAILS - const char* CurrentDetail1; - const char* CurrentDetail2; -#endif - UNITY_LINE_TYPE CurrentTestLineNumber; - UNITY_COUNTER_TYPE NumberOfTests; - UNITY_COUNTER_TYPE TestFailures; - UNITY_COUNTER_TYPE TestIgnores; - UNITY_COUNTER_TYPE CurrentTestFailed; - UNITY_COUNTER_TYPE CurrentTestIgnored; -#ifdef UNITY_INCLUDE_EXEC_TIME - UNITY_TIME_TYPE CurrentTestStartTime; - UNITY_TIME_TYPE CurrentTestStopTime; -#endif -#ifndef UNITY_EXCLUDE_SETJMP_H - jmp_buf AbortFrame; -#endif -}; - -extern struct UNITY_STORAGE_T Unity; - -/*------------------------------------------------------- - * Test Suite Management - *-------------------------------------------------------*/ - -void UnityBegin(const char* filename); -int UnityEnd(void); -void UnitySetTestFile(const char* filename); -void UnityConcludeTest(void); - -#ifndef RUN_TEST -void UnityDefaultTestRun(UnityTestFunction Func, const char* FuncName, const int FuncLineNum); -#else -#define UNITY_SKIP_DEFAULT_RUNNER -#endif - -/*------------------------------------------------------- - * Details Support - *-------------------------------------------------------*/ - -#ifdef UNITY_EXCLUDE_DETAILS -#define UNITY_CLR_DETAILS() -#define UNITY_SET_DETAIL(d1) -#define UNITY_SET_DETAILS(d1,d2) -#else -#define UNITY_CLR_DETAILS() do { Unity.CurrentDetail1 = 0; Unity.CurrentDetail2 = 0; } while (0) -#define UNITY_SET_DETAIL(d1) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = 0; } while (0) -#define UNITY_SET_DETAILS(d1,d2) do { Unity.CurrentDetail1 = (d1); Unity.CurrentDetail2 = (d2); } while (0) - -#ifndef UNITY_DETAIL1_NAME -#define UNITY_DETAIL1_NAME "Function" -#endif - -#ifndef UNITY_DETAIL2_NAME -#define UNITY_DETAIL2_NAME "Argument" -#endif -#endif - -#ifdef UNITY_PRINT_TEST_CONTEXT -void UNITY_PRINT_TEST_CONTEXT(void); -#endif - -/*------------------------------------------------------- - * Test Output - *-------------------------------------------------------*/ - -void UnityPrint(const char* string); - -#ifdef UNITY_INCLUDE_PRINT_FORMATTED -void UnityPrintF(const UNITY_LINE_TYPE line, const char* format, ...); -#endif - -void UnityPrintLen(const char* string, const UNITY_UINT32 length); -void UnityPrintMask(const UNITY_UINT mask, const UNITY_UINT number); -void UnityPrintNumberByStyle(const UNITY_INT number, const UNITY_DISPLAY_STYLE_T style); -void UnityPrintNumber(const UNITY_INT number_to_print); -void UnityPrintNumberUnsigned(const UNITY_UINT number); -void UnityPrintNumberHex(const UNITY_UINT number, const char nibbles_to_print); - -#ifndef UNITY_EXCLUDE_FLOAT_PRINT -void UnityPrintFloat(const UNITY_DOUBLE input_number); -#endif - -/*------------------------------------------------------- - * Test Assertion Functions - *------------------------------------------------------- - * Use the macros below this section instead of calling - * these directly. The macros have a consistent naming - * convention and will pull in file and line information - * for you. */ - -void UnityAssertEqualNumber(const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityAssertGreaterOrLessOrEqualNumber(const UNITY_INT threshold, - const UNITY_INT actual, - const UNITY_COMPARISON_T compare, - const char *msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityAssertEqualIntArray(UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags); - -void UnityAssertBits(const UNITY_INT mask, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualString(const char* expected, - const char* actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualStringLen(const char* expected, - const char* actual, - const UNITY_UINT32 length, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualStringArray( UNITY_INTERNAL_PTR expected, - const char** actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertEqualMemory( UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 length, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertNumbersWithin(const UNITY_UINT delta, - const UNITY_INT expected, - const UNITY_INT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style); - -void UnityAssertNumbersArrayWithin(const UNITY_UINT delta, - UNITY_INTERNAL_PTR expected, - UNITY_INTERNAL_PTR actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_DISPLAY_STYLE_T style, - const UNITY_FLAGS_T flags); - -#ifndef UNITY_EXCLUDE_SETJMP_H -UNITY_NORETURN void UnityFail(const char* message, const UNITY_LINE_TYPE line); -UNITY_NORETURN void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); -#else -void UnityFail(const char* message, const UNITY_LINE_TYPE line); -void UnityIgnore(const char* message, const UNITY_LINE_TYPE line); -#endif - -void UnityMessage(const char* message, const UNITY_LINE_TYPE line); - -#ifndef UNITY_EXCLUDE_FLOAT -void UnityAssertFloatsWithin(const UNITY_FLOAT delta, - const UNITY_FLOAT expected, - const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualFloatArray(UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* expected, - UNITY_PTR_ATTRIBUTE const UNITY_FLOAT* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertFloatSpecial(const UNITY_FLOAT actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style); -#endif - -#ifndef UNITY_EXCLUDE_DOUBLE -void UnityAssertDoublesWithin(const UNITY_DOUBLE delta, - const UNITY_DOUBLE expected, - const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber); - -void UnityAssertEqualDoubleArray(UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* expected, - UNITY_PTR_ATTRIBUTE const UNITY_DOUBLE* actual, - const UNITY_UINT32 num_elements, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLAGS_T flags); - -void UnityAssertDoubleSpecial(const UNITY_DOUBLE actual, - const char* msg, - const UNITY_LINE_TYPE lineNumber, - const UNITY_FLOAT_TRAIT_T style); -#endif - -/*------------------------------------------------------- - * Helpers - *-------------------------------------------------------*/ - -UNITY_INTERNAL_PTR UnityNumToPtr(const UNITY_INT num, const UNITY_UINT8 size); -#ifndef UNITY_EXCLUDE_FLOAT -UNITY_INTERNAL_PTR UnityFloatToPtr(const float num); -#endif -#ifndef UNITY_EXCLUDE_DOUBLE -UNITY_INTERNAL_PTR UnityDoubleToPtr(const double num); -#endif - -/*------------------------------------------------------- - * Error Strings We Might Need - *-------------------------------------------------------*/ - -extern const char UnityStrOk[]; -extern const char UnityStrPass[]; -extern const char UnityStrFail[]; -extern const char UnityStrIgnore[]; - -extern const char UnityStrErrFloat[]; -extern const char UnityStrErrDouble[]; -extern const char UnityStrErr64[]; -extern const char UnityStrErrShorthand[]; - -/*------------------------------------------------------- - * Test Running Macros - *-------------------------------------------------------*/ - -#ifndef UNITY_EXCLUDE_SETJMP_H -#define TEST_PROTECT() (setjmp(Unity.AbortFrame) == 0) -#define TEST_ABORT() longjmp(Unity.AbortFrame, 1) -#else -#define TEST_PROTECT() 1 -#define TEST_ABORT() return -#endif - -/* This tricky series of macros gives us an optional line argument to treat it as RUN_TEST(func, num=__LINE__) */ -#ifndef RUN_TEST -#ifdef __STDC_VERSION__ -#if __STDC_VERSION__ >= 199901L -#define UNITY_SUPPORT_VARIADIC_MACROS -#endif -#endif -#ifdef UNITY_SUPPORT_VARIADIC_MACROS -#define RUN_TEST(...) RUN_TEST_AT_LINE(__VA_ARGS__, __LINE__, throwaway) -#define RUN_TEST_AT_LINE(func, line, ...) UnityDefaultTestRun(func, #func, line) -#endif -#endif - -/* If we can't do the tricky version, we'll just have to require them to always include the line number */ -#ifndef RUN_TEST -#ifdef CMOCK -#define RUN_TEST(func, num) UnityDefaultTestRun(func, #func, num) -#else -#define RUN_TEST(func) UnityDefaultTestRun(func, #func, __LINE__) -#endif -#endif - -#define TEST_LINE_NUM (Unity.CurrentTestLineNumber) -#define TEST_IS_IGNORED (Unity.CurrentTestIgnored) -#define UNITY_NEW_TEST(a) \ - Unity.CurrentTestName = (a); \ - Unity.CurrentTestLineNumber = (UNITY_LINE_TYPE)(__LINE__); \ - Unity.NumberOfTests++; - -#ifndef UNITY_BEGIN -#define UNITY_BEGIN() UnityBegin(__FILE__) -#endif - -#ifndef UNITY_END -#define UNITY_END() UnityEnd() -#endif - -#ifndef UNITY_SHORTHAND_AS_INT -#ifndef UNITY_SHORTHAND_AS_MEM -#ifndef UNITY_SHORTHAND_AS_NONE -#ifndef UNITY_SHORTHAND_AS_RAW -#define UNITY_SHORTHAND_AS_OLD -#endif -#endif -#endif -#endif - -/*----------------------------------------------- - * Command Line Argument Support - *-----------------------------------------------*/ - -#ifdef UNITY_USE_COMMAND_LINE_ARGS -int UnityParseOptions(int argc, char** argv); -int UnityTestMatches(void); -#endif - -/*------------------------------------------------------- - * Basic Fail and Ignore - *-------------------------------------------------------*/ - -#define UNITY_TEST_FAIL(line, message) UnityFail( (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_IGNORE(line, message) UnityIgnore( (message), (UNITY_LINE_TYPE)(line)) - -/*------------------------------------------------------- - * Test Asserts - *-------------------------------------------------------*/ - -#define UNITY_TEST_ASSERT(condition, line, message) do { if (condition) { /* nothing*/ } else { UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), (message)); } } while (0) -#define UNITY_TEST_ASSERT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) == NULL), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_NOT_NULL(pointer, line, message) UNITY_TEST_ASSERT(((pointer) != NULL), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) == 0), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_NOT_EMPTY(pointer, line, message) UNITY_TEST_ASSERT(((pointer[0]) != 0), (UNITY_LINE_TYPE)(line), (message)) - -#define UNITY_TEST_ASSERT_EQUAL_INT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_EQUAL_INT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_EQUAL_INT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_EQUAL_INT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_EQUAL_UINT(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_EQUAL_UINT8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_EQUAL_UINT16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_EQUAL_UINT32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_EQUAL_HEX8(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_EQUAL_HEX16(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT16)(expected), (UNITY_INT)(UNITY_INT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_EQUAL_HEX32(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT32)(expected), (UNITY_INT)(UNITY_INT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_EQUAL_CHAR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(UNITY_INT8 )(expected), (UNITY_INT)(UNITY_INT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) -#define UNITY_TEST_ASSERT_BITS(mask, expected, actual, line, message) UnityAssertBits((UNITY_INT)(mask), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line)) - -#define UNITY_TEST_ASSERT_NOT_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_GREATER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_SMALLER_THAN_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 )(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16) (threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32) (threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 ) (threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT16)(threshold), (UNITY_INT)(UNITY_INT16) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT32)(threshold), (UNITY_INT)(UNITY_INT32) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT) (threshold), (UNITY_INT) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT8 )(threshold), (UNITY_INT)(UNITY_UINT8 )(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT16)(threshold), (UNITY_INT)(UNITY_UINT16)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_UINT32)(threshold), (UNITY_INT)(UNITY_UINT32)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(UNITY_INT8 )(threshold), (UNITY_INT)(UNITY_INT8 ) (actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_INT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT) -#define UNITY_TEST_ASSERT_INT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8) -#define UNITY_TEST_ASSERT_INT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_INT16) (expected), (UNITY_INT)(UNITY_INT16) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16) -#define UNITY_TEST_ASSERT_INT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_INT32) (expected), (UNITY_INT)(UNITY_INT32) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32) -#define UNITY_TEST_ASSERT_UINT_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin( (delta), (UNITY_INT) (expected), (UNITY_INT) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT) -#define UNITY_TEST_ASSERT_UINT8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8) -#define UNITY_TEST_ASSERT_UINT16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16) -#define UNITY_TEST_ASSERT_UINT32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32) -#define UNITY_TEST_ASSERT_HEX8_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT8 )(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8) -#define UNITY_TEST_ASSERT_HEX16_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT16)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT16)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16) -#define UNITY_TEST_ASSERT_HEX32_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT32)(delta), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(expected), (UNITY_INT)(UNITY_UINT)(UNITY_UINT32)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32) -#define UNITY_TEST_ASSERT_CHAR_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((UNITY_UINT8 )(delta), (UNITY_INT)(UNITY_INT8 ) (expected), (UNITY_INT)(UNITY_INT8 ) (actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR) - -#define UNITY_TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin( (delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT16)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT32)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT8 )(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), ((UNITY_UINT32)(num_elements)), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) - - -#define UNITY_TEST_ASSERT_EQUAL_PTR(expected, actual, line, message) UnityAssertEqualNumber((UNITY_PTR_TO_INT)(expected), (UNITY_PTR_TO_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER) -#define UNITY_TEST_ASSERT_EQUAL_STRING(expected, actual, line, message) UnityAssertEqualString((const char*)(expected), (const char*)(actual), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len, line, message) UnityAssertEqualStringLen((const char*)(expected), (const char*)(actual), (UNITY_UINT32)(len), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_MEMORY(expected, actual, len, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), 1, (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) - -#define UNITY_TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_ARRAY) - -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT) (expected), (UNITY_INT_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT16)(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT32)(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX8, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT16 )(expected), 2), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX16, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT32 )(expected), 4), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX32, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_PTR_TO_INT) (expected), (UNITY_POINTER_WIDTH / 8)), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_POINTER, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements, line, message) UnityAssertEqualStringArray((UNITY_INTERNAL_PTR)(expected), (const char**)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements, line, message) UnityAssertEqualMemory((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(len), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT8 )(expected), 1), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_CHAR, UNITY_ARRAY_TO_VAL) - -#ifdef UNITY_SUPPORT_64 -#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UnityAssertEqualNumber((UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualIntArray((UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_UINT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements, line, message) UnityAssertEqualIntArray(UnityNumToPtr((UNITY_INT)(UNITY_INT64)(expected), 8), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UnityAssertNumbersWithin((delta), (UNITY_INT)(expected), (UNITY_INT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_NOT_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_GREATER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_THAN, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UnityAssertGreaterOrLessOrEqualNumber((UNITY_INT)(threshold), (UNITY_INT)(actual), UNITY_SMALLER_OR_EQUAL, (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64) -#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_INT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_UINT64, UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UnityAssertNumbersArrayWithin((UNITY_UINT64)(delta), (UNITY_INTERNAL_PTR)(expected), (UNITY_INTERNAL_PTR)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_DISPLAY_STYLE_HEX64, UNITY_ARRAY_TO_ARRAY) -#else -#define UNITY_TEST_ASSERT_EQUAL_INT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_INT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_UINT64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_HEX64_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_THAN_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64(threshold, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#define UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErr64) -#endif - -#ifdef UNITY_EXCLUDE_FLOAT -#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrFloat) -#else -#define UNITY_TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual, line, message) UnityAssertFloatsWithin((UNITY_FLOAT)(delta), (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT(expected, actual, line, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((UNITY_FLOAT)(expected) * (UNITY_FLOAT)UNITY_FLOAT_PRECISION, (UNITY_FLOAT)(expected), (UNITY_FLOAT)(actual), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray((UNITY_FLOAT*)(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements, line, message) UnityAssertEqualFloatArray(UnityFloatToPtr(expected), (UNITY_FLOAT*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_FLOAT_IS_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) -#define UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) -#define UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual, line, message) UnityAssertFloatSpecial((UNITY_FLOAT)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) -#endif - -#ifdef UNITY_EXCLUDE_DOUBLE -#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UNITY_TEST_FAIL((UNITY_LINE_TYPE)(line), UnityStrErrDouble) -#else -#define UNITY_TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual, line, message) UnityAssertDoublesWithin((UNITY_DOUBLE)(delta), (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line)) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE(expected, actual, line, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((UNITY_DOUBLE)(expected) * (UNITY_DOUBLE)UNITY_DOUBLE_PRECISION, (UNITY_DOUBLE)(expected), (UNITY_DOUBLE)(actual), (UNITY_LINE_TYPE)(line), (message)) -#define UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray((UNITY_DOUBLE*)(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_ARRAY) -#define UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements, line, message) UnityAssertEqualDoubleArray(UnityDoubleToPtr(expected), (UNITY_DOUBLE*)(actual), (UNITY_UINT32)(num_elements), (message), (UNITY_LINE_TYPE)(line), UNITY_ARRAY_TO_VAL) -#define UNITY_TEST_ASSERT_DOUBLE_IS_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NEG_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NAN) -#define UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_DET) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NEG_INF) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_NAN) -#define UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual, line, message) UnityAssertDoubleSpecial((UNITY_DOUBLE)(actual), (message), (UNITY_LINE_TYPE)(line), UNITY_FLOAT_IS_NOT_DET) -#endif - -/* End of UNITY_INTERNALS_H */ -#endif diff --git a/taskchampion/integration-tests/src/bindings_tests/uuid.c b/taskchampion/integration-tests/src/bindings_tests/uuid.c deleted file mode 100644 index 83b02482d..000000000 --- a/taskchampion/integration-tests/src/bindings_tests/uuid.c +++ /dev/null @@ -1,72 +0,0 @@ -#include -#include "unity.h" -#include "taskchampion.h" - -// creating UUIDs does not crash -static void test_uuid_creation(void) { - tc_uuid_new_v4(); - tc_uuid_nil(); -} - -// converting UUIDs to a buf works -static void test_uuid_to_buf(void) { - TEST_ASSERT_EQUAL(TC_UUID_STRING_BYTES, 36); - - TCUuid u2 = tc_uuid_nil(); - - char u2str[TC_UUID_STRING_BYTES]; - tc_uuid_to_buf(u2, u2str); - TEST_ASSERT_EQUAL_MEMORY("00000000-0000-0000-0000-000000000000", u2str, TC_UUID_STRING_BYTES); -} - -// converting UUIDs to a buf works -static void test_uuid_to_str(void) { - TCUuid u = tc_uuid_nil(); - TCString s = tc_uuid_to_str(u); - TEST_ASSERT_EQUAL_STRING( - "00000000-0000-0000-0000-000000000000", - tc_string_content(&s)); - tc_string_free(&s); -} - -// converting valid UUIDs from string works -static void test_uuid_valid_from_str(void) { - TCUuid u; - char *ustr = "23cb25e0-5d1a-4932-8131-594ac6d3a843"; - TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow(ustr), &u)); - TEST_ASSERT_EQUAL(0x23, u.bytes[0]); - TEST_ASSERT_EQUAL(0x43, u.bytes[15]); -} - -// converting invalid UUIDs from string fails as expected -static void test_uuid_invalid_string_fails(void) { - TCUuid u; - char *ustr = "not-a-valid-uuid"; - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u)); -} - -// converting invalid UTF-8 UUIDs from string fails as expected -static void test_uuid_bad_utf8(void) { - TCUuid u; - char *ustr = "\xf0\x28\x8c\xbc"; - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u)); -} - -// converting a string with embedded NUL fails as expected -static void test_uuid_embedded_nul(void) { - TCUuid u; - TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_clone_with_len("ab\0de", 5), &u)); -} - -int uuid_tests(void) { - UNITY_BEGIN(); - // each test case above should be named here, in order. - RUN_TEST(test_uuid_creation); - RUN_TEST(test_uuid_valid_from_str); - RUN_TEST(test_uuid_to_buf); - RUN_TEST(test_uuid_to_str); - RUN_TEST(test_uuid_invalid_string_fails); - RUN_TEST(test_uuid_bad_utf8); - RUN_TEST(test_uuid_embedded_nul); - return UNITY_END(); -} diff --git a/taskchampion/integration-tests/src/lib.rs b/taskchampion/integration-tests/src/lib.rs deleted file mode 100644 index 3d76f3008..000000000 --- a/taskchampion/integration-tests/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod bindings_tests; -pub use taskchampion_lib::*; diff --git a/taskchampion/integration-tests/tests/bindings.rs b/taskchampion/integration-tests/tests/bindings.rs deleted file mode 100644 index a121dd721..000000000 --- a/taskchampion/integration-tests/tests/bindings.rs +++ /dev/null @@ -1,31 +0,0 @@ -use lazy_static::lazy_static; -use std::sync::Mutex; -use tempfile::TempDir; - -lazy_static! { - // the C library running the tests is not reentrant, so we use a mutex to ensure that only one - // test runs at a time. - static ref MUTEX: Mutex<()> = Mutex::new(()); -} - -macro_rules! suite( - { $s:ident } => { - #[test] - fn $s() { - let tmp_dir = TempDir::new().expect("TempDir failed"); - let (res, output) = { - let _guard = MUTEX.lock().unwrap(); - // run the tests in the temp dir (NOTE: this must be inside - // the mutex guard!) - std::env::set_current_dir(tmp_dir.as_ref()).unwrap(); - integration_tests::bindings_tests::$s() - }; - println!("{}", output); - if res != 0 { - assert!(false, "test failed"); - } - } - }; -); - -include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs")); diff --git a/taskchampion/integration-tests/tests/cross-sync.rs b/taskchampion/integration-tests/tests/cross-sync.rs deleted file mode 100644 index 6cac3c293..000000000 --- a/taskchampion/integration-tests/tests/cross-sync.rs +++ /dev/null @@ -1,66 +0,0 @@ -use pretty_assertions::assert_eq; -use taskchampion::{Replica, ServerConfig, Status, StorageConfig}; -use tempfile::TempDir; - -#[test] -fn cross_sync() -> anyhow::Result<()> { - // set up two replicas, and demonstrate replication between them - let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); - let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); - - let tmp_dir = TempDir::new().expect("TempDir failed"); - let server_config = ServerConfig::Local { - server_dir: tmp_dir.path().to_path_buf(), - }; - let mut server = server_config.into_server()?; - - // add some tasks on rep1 - let t1 = rep1.new_task(Status::Pending, "test 1".into())?; - let t2 = rep1.new_task(Status::Pending, "test 2".into())?; - - // modify t1 - let mut t1 = t1.into_mut(&mut rep1); - t1.start()?; - let t1 = t1.into_immut(); - - rep1.sync(&mut server, false)?; - rep2.sync(&mut server, false)?; - - // those tasks should exist on rep2 now - let t12 = rep2 - .get_task(t1.get_uuid())? - .expect("expected task 1 on rep2"); - let t22 = rep2 - .get_task(t2.get_uuid())? - .expect("expected task 2 on rep2"); - - assert_eq!(t12.get_description(), "test 1"); - assert_eq!(t12.is_active(), true); - assert_eq!(t22.get_description(), "test 2"); - assert_eq!(t22.is_active(), false); - - // make non-conflicting changes on the two replicas - let mut t2 = t2.into_mut(&mut rep1); - t2.set_status(Status::Completed)?; - let t2 = t2.into_immut(); - - let mut t12 = t12.into_mut(&mut rep2); - t12.set_status(Status::Completed)?; - - // sync those changes back and forth - rep1.sync(&mut server, false)?; // rep1 -> server - rep2.sync(&mut server, false)?; // server -> rep2, rep2 -> server - rep1.sync(&mut server, false)?; // server -> rep1 - - let t1 = rep1 - .get_task(t1.get_uuid())? - .expect("expected task 1 on rep1"); - assert_eq!(t1.get_status(), Status::Completed); - - let t22 = rep2 - .get_task(t2.get_uuid())? - .expect("expected task 2 on rep2"); - assert_eq!(t22.get_status(), Status::Completed); - - Ok(()) -} diff --git a/taskchampion/integration-tests/tests/update-and-delete-sync.rs b/taskchampion/integration-tests/tests/update-and-delete-sync.rs deleted file mode 100644 index 91727c7e1..000000000 --- a/taskchampion/integration-tests/tests/update-and-delete-sync.rs +++ /dev/null @@ -1,72 +0,0 @@ -use taskchampion::chrono::{TimeZone, Utc}; -use taskchampion::{Replica, ServerConfig, Status, StorageConfig}; -use tempfile::TempDir; - -#[test] -fn update_and_delete_sync_delete_first() -> anyhow::Result<()> { - update_and_delete_sync(true) -} - -#[test] -fn update_and_delete_sync_update_first() -> anyhow::Result<()> { - update_and_delete_sync(false) -} - -/// Test what happens when an update is sync'd into a repo after a task is deleted. -/// If delete_first, then the deletion is sync'd to the server first; otherwise -/// the update is sync'd first. Either way, the task is gone. -fn update_and_delete_sync(delete_first: bool) -> anyhow::Result<()> { - // set up two replicas, and demonstrate replication between them - let mut rep1 = Replica::new(StorageConfig::InMemory.into_storage()?); - let mut rep2 = Replica::new(StorageConfig::InMemory.into_storage()?); - - let tmp_dir = TempDir::new().expect("TempDir failed"); - let mut server = ServerConfig::Local { - server_dir: tmp_dir.path().to_path_buf(), - } - .into_server()?; - - // add a task on rep1, and sync it to rep2 - let t = rep1.new_task(Status::Pending, "test task".into())?; - let u = t.get_uuid(); - - rep1.sync(&mut server, false)?; - rep2.sync(&mut server, false)?; - - // mark the task as deleted, long in the past, on rep2 - { - let mut t = rep2.get_task(u)?.unwrap().into_mut(&mut rep2); - t.delete()?; - t.set_modified(Utc.ymd(1980, 1, 1).and_hms(0, 0, 0))?; - } - - // sync it back to rep1 - rep2.sync(&mut server, false)?; - rep1.sync(&mut server, false)?; - - // expire the task on rep1 and check that it is gone locally - rep1.expire_tasks()?; - assert!(rep1.get_task(u)?.is_none()); - - // modify the task on rep2 - { - let mut t = rep2.get_task(u)?.unwrap().into_mut(&mut rep2); - t.set_description("modified".to_string())?; - } - - // sync back and forth - if delete_first { - rep1.sync(&mut server, false)?; - } - rep2.sync(&mut server, false)?; - rep1.sync(&mut server, false)?; - if !delete_first { - rep2.sync(&mut server, false)?; - } - - // check that the task is gone on both replicas - assert!(rep1.get_task(u)?.is_none()); - assert!(rep2.get_task(u)?.is_none()); - - Ok(()) -} diff --git a/taskchampion/lib/Cargo.toml b/taskchampion/lib/Cargo.toml deleted file mode 100644 index e9257da28..000000000 --- a/taskchampion/lib/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "taskchampion-lib" -version = "0.1.0" -edition = "2021" - -[lib] -crate-type = ["staticlib", "rlib"] - -[dependencies] -libc.workspace = true -anyhow.workspace = true -ffizz-header.workspace = true - -taskchampion = { path = "../taskchampion" } - -[dev-dependencies] -pretty_assertions.workspace = true diff --git a/taskchampion/lib/Makefile b/taskchampion/lib/Makefile deleted file mode 100644 index d2dbe101b..000000000 --- a/taskchampion/lib/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so - cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@ diff --git a/taskchampion/lib/src/annotation.rs b/taskchampion/lib/src/annotation.rs deleted file mode 100644 index 5edfda4c8..000000000 --- a/taskchampion/lib/src/annotation.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use taskchampion::chrono::prelude::*; - -#[ffizz_header::item] -#[ffizz(order = 400)] -/// ***** TCAnnotation ***** -/// -/// TCAnnotation contains the details of an annotation. -/// -/// # Safety -/// -/// An annotation must be initialized from a tc_.. function, and later freed -/// with `tc_annotation_free` or `tc_annotation_list_free`. -/// -/// Any function taking a `*TCAnnotation` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - ownership transfers to the called function, and the value must not be used -/// after the call returns. In fact, the value will be zeroed out to ensure this. -/// -/// TCAnnotations are not threadsafe. -/// -/// ```c -/// typedef struct TCAnnotation { -/// // Time the annotation was made. Must be nonzero. -/// time_t entry; -/// // Content of the annotation. Must not be NULL. -/// TCString description; -/// } TCAnnotation; -/// ``` -#[repr(C)] -pub struct TCAnnotation { - pub entry: libc::time_t, - pub description: TCString, -} - -impl PassByValue for TCAnnotation { - // NOTE: we cannot use `RustType = Annotation` here because conversion of the - // Rust to a String can fail. - type RustType = (DateTime, RustString<'static>); - - unsafe fn from_ctype(mut self) -> Self::RustType { - // SAFETY: - // - any time_t value is valid - // - time_t is copy, so ownership is not important - let entry = unsafe { libc::time_t::val_from_arg(self.entry) }.unwrap(); - // SAFETY: - // - self.description is valid (came from return_val in as_ctype) - // - self is owned, so we can take ownership of this TCString - let description = - unsafe { TCString::take_val_from_arg(&mut self.description, TCString::default()) }; - (entry, description) - } - - fn as_ctype((entry, description): Self::RustType) -> Self { - TCAnnotation { - entry: libc::time_t::as_ctype(Some(entry)), - // SAFETY: - // - ownership of the TCString tied to ownership of Self - description: unsafe { TCString::return_val(description) }, - } - } -} - -impl Default for TCAnnotation { - fn default() -> Self { - TCAnnotation { - entry: 0 as libc::time_t, - description: TCString::default(), - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 410)] -/// ***** TCAnnotationList ***** -/// -/// TCAnnotationList represents a list of annotations. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCAnnotationList { -/// // number of annotations in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by -/// // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. -/// struct TCAnnotation *items; -/// } TCAnnotationList; -/// ``` -#[repr(C)] -pub struct TCAnnotationList { - len: libc::size_t, - /// total size of items (internal use only) - capacity: libc::size_t, - items: *mut TCAnnotation, -} - -impl CList for TCAnnotationList { - type Element = TCAnnotation; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCAnnotationList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 401)] -/// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used -/// after this call. -/// -/// ```c -/// EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) { - debug_assert!(!tcann.is_null()); - // SAFETY: - // - tcann is not NULL - // - *tcann is a valid TCAnnotation (caller promised to treat it as read-only) - let annotation = unsafe { TCAnnotation::take_val_from_arg(tcann, TCAnnotation::default()) }; - drop(annotation); -} - -#[ffizz_header::item] -#[ffizz(order = 411)] -/// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. -/// -/// ```c -/// EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) { - // SAFETY: - // - tcanns is not NULL and points to a valid TCAnnotationList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcanns) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; - assert!(!tcanns.items.is_null()); - assert_eq!(tcanns.len, 0); - assert_eq!(tcanns.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_annotation_list_free(&mut tcanns) }; - assert!(tcanns.items.is_null()); - assert_eq!(tcanns.len, 0); - assert_eq!(tcanns.capacity, 0); - } -} diff --git a/taskchampion/lib/src/atomic.rs b/taskchampion/lib/src/atomic.rs deleted file mode 100644 index 01c72059e..000000000 --- a/taskchampion/lib/src/atomic.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Trait implementations for a few atomic types - -use crate::traits::*; -use taskchampion::chrono::{DateTime, Utc}; -use taskchampion::utc_timestamp; - -impl PassByValue for usize { - type RustType = usize; - - unsafe fn from_ctype(self) -> usize { - self - } - - fn as_ctype(arg: usize) -> usize { - arg - } -} - -/// Convert an Option> to a libc::time_t, or zero if not set. -impl PassByValue for libc::time_t { - type RustType = Option>; - - unsafe fn from_ctype(self) -> Option> { - if self != 0 { - return Some(utc_timestamp(self)); - } - None - } - - fn as_ctype(arg: Option>) -> libc::time_t { - arg.map(|ts| ts.timestamp() as libc::time_t) - .unwrap_or(0 as libc::time_t) - } -} diff --git a/taskchampion/lib/src/kv.rs b/taskchampion/lib/src/kv.rs deleted file mode 100644 index 3831c7016..000000000 --- a/taskchampion/lib/src/kv.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -#[ffizz_header::item] -#[ffizz(order = 600)] -/// ***** TCKV ***** -/// -/// TCKV contains a key/value pair that is part of a task. -/// -/// Neither key nor value are ever NULL. They remain owned by the TCKV and -/// will be freed when it is freed with tc_kv_list_free. -/// -/// ```c -/// typedef struct TCKV { -/// struct TCString key; -/// struct TCString value; -/// } TCKV; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCKV { - pub key: TCString, - pub value: TCString, -} - -impl PassByValue for TCKV { - type RustType = (RustString<'static>, RustString<'static>); - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - self.key is not NULL (field docstring) - // - self.key came from return_ptr in as_ctype - // - self is owned, so we can take ownership of this TCString - let key = unsafe { TCString::val_from_arg(self.key) }; - // SAFETY: (same) - let value = unsafe { TCString::val_from_arg(self.value) }; - (key, value) - } - - fn as_ctype((key, value): Self::RustType) -> Self { - TCKV { - // SAFETY: - // - ownership of the TCString tied to ownership of Self - key: unsafe { TCString::return_val(key) }, - // SAFETY: - // - ownership of the TCString tied to ownership of Self - value: unsafe { TCString::return_val(value) }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 610)] -/// ***** TCKVList ***** -/// -/// TCKVList represents a list of key/value pairs. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCKVList { -/// // number of key/value pairs in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by -/// // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. -/// struct TCKV *items; -/// } TCKVList; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCKVList { - pub len: libc::size_t, - /// total size of items (internal use only) - pub _capacity: libc::size_t, - pub items: *mut TCKV, -} - -impl CList for TCKVList { - type Element = TCKV; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCKVList { - len, - _capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -impl Default for TCKVList { - fn default() -> Self { - // SAFETY: - // - caller will free this list - unsafe { TCKVList::return_val(Vec::new()) } - } -} - -// NOTE: callers never have a TCKV that is not in a list, so there is no tc_kv_free. - -#[ffizz_header::item] -#[ffizz(order = 611)] -/// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. -/// -/// ```c -/// EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_kv_list_free(tckvs: *mut TCKVList) { - // SAFETY: - // - tckvs is not NULL and points to a valid TCKVList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tckvs) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tckvs = unsafe { TCKVList::return_val(Vec::new()) }; - assert!(!tckvs.items.is_null()); - assert_eq!(tckvs.len, 0); - assert_eq!(tckvs._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tckvs = unsafe { TCKVList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_kv_list_free(&mut tckvs) }; - assert!(tckvs.items.is_null()); - assert_eq!(tckvs.len, 0); - assert_eq!(tckvs._capacity, 0); - } -} diff --git a/taskchampion/lib/src/lib.rs b/taskchampion/lib/src/lib.rs deleted file mode 100644 index ad1fc31f7..000000000 --- a/taskchampion/lib/src/lib.rs +++ /dev/null @@ -1,172 +0,0 @@ -#![warn(unsafe_op_in_unsafe_fn)] -#![allow(unused_unsafe)] -// Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020 -// #![warn(clippy::undocumented_unsafe_blocks)] - -// docstrings for extern "C" functions are reflected into C, and do not benefit -// from safety docs. -#![allow(clippy::missing_safety_doc)] -// deny some things that are typically warnings -#![deny(clippy::derivable_impls)] -#![deny(clippy::wrong_self_convention)] -#![deny(clippy::extra_unused_lifetimes)] -#![deny(clippy::unnecessary_to_owned)] - -// ffizz_header orders: -// -// 000-099: header matter -// 100-199: TCResult -// 200-299: TCString / List -// 300-399: TCUuid / List -// 400-499: TCAnnotation / List -// 500-599: TCUda / List -// 600-699: TCKV / List -// 700-799: TCStatus -// 800-899: TCServer -// 900-999: TCReplica -// 1000-1099: TCTask / List -// 1100-1199: TCWorkingSet -// 10000-10099: footer - -ffizz_header::snippet! { -#[ffizz(name="intro", order=0)] -/// TaskChampion -/// -/// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust -/// `taskchampion` crate. Refer to the documentation for that crate at -/// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file -/// focus mostly on the low-level details of passing values to and from TaskChampion. -/// -/// # Overview -/// -/// This library defines four major types used to interact with the API, that map directly to Rust -/// types. -/// -/// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html -/// * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html -/// * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html -/// * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html -/// -/// It also defines a few utility types: -/// -/// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. -/// * TC…List - a list of objects represented as a C array -/// * see below for the remainder -/// -/// # Safety -/// -/// Each type contains specific instructions to ensure memory safety. The general rules are as -/// follows. -/// -/// No types in this library are threadsafe. All values should be used in only one thread for their -/// entire lifetime. It is safe to use unrelated values in different threads (for example, -/// different threads may use different TCReplica values concurrently). -/// -/// ## Pass by Pointer -/// -/// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers -/// in C. The bytes these pointers address are private to the Rust implementation and must not be -/// accessed from C. -/// -/// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the -/// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where -/// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value -/// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of -/// which passes to Rust when it is used as a function argument. -/// -/// The limited circumstances where one value must not outlive another, due to pointer references -/// between them, are documented below. -/// -/// ## Pass by Value -/// -/// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible -/// from C. C code is free to access the content of these types in a _read_only_ fashion. -/// -/// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing -/// the value or transferring ownership. The tc_…_free functions for these types will replace the -/// pointers with NULL to guard against use-after-free errors. The interior pointers in such values -/// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). -/// -/// TCUuid is a special case, because it does not contain pointers. It can be freely copied and -/// need not be freed. -/// -/// ## Lists -/// -/// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` -/// is an array of length `len`. Lists, and the values in the `items` array, must be treated as -/// read-only. On return from an API function, a list's ownership is with the C caller, which must -/// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an -/// error to free any value in the `items` array of a list. -} - -ffizz_header::snippet! { -#[ffizz(name="topmatter", order=1)] -/// ```c -/// #ifndef TASKCHAMPION_H -/// #define TASKCHAMPION_H -/// -/// #include -/// #include -/// #include -/// -/// #ifdef __cplusplus -/// #define EXTERN_C extern "C" -/// #else -/// #define EXTERN_C -/// #endif // __cplusplus -/// ``` -} - -ffizz_header::snippet! { -#[ffizz(name="bottomatter", order=10000)] -/// ```c -/// #endif /* TASKCHAMPION_H */ -/// ``` -} - -mod traits; -mod util; - -pub mod annotation; -pub use annotation::*; -pub mod atomic; -pub mod kv; -pub use kv::*; -pub mod replica; -pub use replica::*; -pub mod result; -pub use result::*; -pub mod server; -pub use server::*; -pub mod status; -pub use status::*; -pub mod string; -pub use string::*; -pub mod task; -pub use task::*; -pub mod uda; -pub use uda::*; -pub mod uuid; -pub use uuid::*; -pub mod workingset; -pub use workingset::*; - -pub(crate) mod types { - pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList}; - pub(crate) use crate::kv::TCKVList; - pub(crate) use crate::replica::TCReplica; - pub(crate) use crate::result::TCResult; - pub(crate) use crate::server::TCServer; - pub(crate) use crate::status::TCStatus; - pub(crate) use crate::string::{RustString, TCString, TCStringList}; - pub(crate) use crate::task::{TCTask, TCTaskList}; - pub(crate) use crate::uda::{TCUda, TCUdaList, Uda}; - pub(crate) use crate::uuid::{TCUuid, TCUuidList}; - pub(crate) use crate::workingset::TCWorkingSet; -} - -#[cfg(debug_assertions)] -/// Generate the taskchapion.h header -pub fn generate_header() -> String { - ffizz_header::generate() -} diff --git a/taskchampion/lib/src/replica.rs b/taskchampion/lib/src/replica.rs deleted file mode 100644 index e6956f431..000000000 --- a/taskchampion/lib/src/replica.rs +++ /dev/null @@ -1,904 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use std::ptr::NonNull; -use taskchampion::storage::ReplicaOp; -use taskchampion::{Replica, StorageConfig}; - -#[ffizz_header::item] -#[ffizz(order = 900)] -/// ***** TCReplica ***** -/// -/// A replica represents an instance of a user's task data, providing an easy interface -/// for querying and modifying that data. -/// -/// # Error Handling -/// -/// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then -/// `tc_replica_error` will return the error message. -/// -/// # Safety -/// -/// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and -/// must later be freed to avoid a memory leak. -/// -/// Any function taking a `*TCReplica` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. -/// -/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. -/// -/// TCReplicas are not threadsafe. -/// -/// ```c -/// typedef struct TCReplica TCReplica; -/// ``` -pub struct TCReplica { - /// The wrapped Replica - inner: Replica, - - /// If true, this replica has an outstanding &mut (for a TaskMut) - mut_borrowed: bool, - - /// The error from the most recent operation, if any - error: Option>, -} - -impl PassByPointer for TCReplica {} - -impl TCReplica { - /// Mutably borrow the inner Replica - pub(crate) fn borrow_mut(&mut self) -> &mut Replica { - if self.mut_borrowed { - panic!("replica is already borrowed"); - } - self.mut_borrowed = true; - &mut self.inner - } - - /// Release the borrow made by [`borrow_mut`] - pub(crate) fn release_borrow(&mut self) { - if !self.mut_borrowed { - panic!("replica is not borrowed"); - } - self.mut_borrowed = false; - } -} - -impl From for TCReplica { - fn from(rep: Replica) -> TCReplica { - TCReplica { - inner: rep, - mut_borrowed: false, - error: None, - } - } -} - -/// Utility function to allow using `?` notation to return an error value. This makes -/// a mutable borrow, because most Replica methods require a `&mut`. -fn wrap(rep: *mut TCReplica, f: F, err_value: T) -> T -where - F: FnOnce(&mut Replica) -> anyhow::Result, -{ - debug_assert!(!rep.is_null()); - // SAFETY: - // - rep is not NULL (promised by caller) - // - *rep is a valid TCReplica (promised by caller) - // - rep is valid for the duration of this function - // - rep is not modified by anything else (not threadsafe) - let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; - if rep.mut_borrowed { - panic!("replica is borrowed and cannot be used"); - } - rep.error = None; - match f(&mut rep.inner) { - Ok(v) => v, - Err(e) => { - rep.error = Some(err_to_ruststring(e)); - err_value - } - } -} - -/// Utility function to allow using `?` notation to return an error value in the constructor. -fn wrap_constructor(f: F, error_out: *mut TCString, err_value: T) -> T -where - F: FnOnce() -> anyhow::Result, -{ - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { *error_out = TCString::default() }; - } - - match f() { - Ok(v) => v, - Err(e) => { - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { - TCString::val_to_arg_out(err_to_ruststring(e), error_out); - } - } - err_value - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 900)] -/// ***** TCReplicaOpType ***** -/// -/// ```c -/// enum TCReplicaOpType -/// #ifdef __cplusplus -/// : uint32_t -/// #endif // __cplusplus -/// { -/// Create = 0, -/// Delete = 1, -/// Update = 2, -/// UndoPoint = 3, -/// }; -/// #ifndef __cplusplus -/// typedef uint32_t TCReplicaOpType; -/// #endif // __cplusplus -/// ``` -#[derive(Debug, Default)] -#[repr(u32)] -pub enum TCReplicaOpType { - Create = 0, - Delete = 1, - Update = 2, - UndoPoint = 3, - #[default] - Error = 4, -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// Create a new TCReplica with an in-memory database. The contents of the database will be -/// lost when it is freed with tc_replica_free. -/// -/// ```c -/// EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica { - let storage = StorageConfig::InMemory - .into_storage() - .expect("in-memory always succeeds"); - // SAFETY: - // - caller promises to free this value - unsafe { TCReplica::from(Replica::new(storage)).return_ptr() } -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// Create a new TCReplica with an on-disk database having the given filename. On error, a string -/// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller -/// must free this string. -/// -/// ```c -/// EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, -/// bool create_if_missing, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_on_disk( - path: TCString, - create_if_missing: bool, - error_out: *mut TCString, -) -> *mut TCReplica { - wrap_constructor( - || { - // SAFETY: - // - path is valid (promised by caller) - // - caller will not use path after this call (convention) - let mut path = unsafe { TCString::val_from_arg(path) }; - let storage = StorageConfig::OnDisk { - taskdb_dir: path.to_path_buf_mut()?, - create_if_missing, - } - .into_storage()?; - - // SAFETY: - // - caller promises to free this value - Ok(unsafe { TCReplica::from(Replica::new(storage)).return_ptr() }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// ***** TCReplicaOp ***** -/// -/// ```c -/// struct TCReplicaOp { -/// TCReplicaOpType operation_type; -/// void* inner; -/// }; -/// -/// typedef struct TCReplicaOp TCReplicaOp; -/// ``` -#[derive(Debug)] -#[repr(C)] -pub struct TCReplicaOp { - operation_type: TCReplicaOpType, - inner: Box, -} - -impl From for TCReplicaOp { - fn from(replica_op: ReplicaOp) -> TCReplicaOp { - match replica_op { - ReplicaOp::Create { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Create, - inner: Box::new(replica_op), - }, - ReplicaOp::Delete { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Delete, - inner: Box::new(replica_op), - }, - ReplicaOp::Update { .. } => TCReplicaOp { - operation_type: TCReplicaOpType::Update, - inner: Box::new(replica_op), - }, - ReplicaOp::UndoPoint => TCReplicaOp { - operation_type: TCReplicaOpType::UndoPoint, - inner: Box::new(replica_op), - }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 901)] -/// ***** TCReplicaOpList ***** -/// -/// ```c -/// struct TCReplicaOpList { -/// struct TCReplicaOp *items; -/// size_t len; -/// size_t capacity; -/// }; -/// -/// typedef struct TCReplicaOpList TCReplicaOpList; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCReplicaOpList { - items: *mut TCReplicaOp, - len: usize, - capacity: usize, -} - -impl Default for TCReplicaOpList { - fn default() -> Self { - // SAFETY: - // - caller will free this value - unsafe { TCReplicaOpList::return_val(Vec::new()) } - } -} - -impl CList for TCReplicaOpList { - type Element = TCReplicaOp; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCReplicaOpList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get a list of all tasks in the replica. -/// -/// Returns a TCTaskList with a NULL items field on error. -/// -/// ```c -/// EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList { - wrap( - rep, - |rep| { - // note that the Replica API returns a hashmap here, but we discard - // the keys and return a simple list. The task UUIDs are available - // from task.get_uuid(), so information is not lost. - let tasks: Vec<_> = rep - .all_tasks()? - .drain() - .map(|(_uuid, t)| { - Some( - NonNull::new( - // SAFETY: - // - caller promises to free this value (via freeing the list) - unsafe { TCTask::from(t).return_ptr() }, - ) - .expect("TCTask::return_ptr returned NULL"), - ) - }) - .collect(); - // SAFETY: - // - value is not allocated and need not be freed - Ok(unsafe { TCTaskList::return_val(tasks) }) - }, - TCTaskList::null_value(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get a list of all uuids for tasks in the replica. -/// -/// Returns a TCUuidList with a NULL items field on error. -/// -/// The caller must free the UUID list with `tc_uuid_list_free`. -/// -/// ```c -/// EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList { - wrap( - rep, - |rep| { - let uuids: Vec<_> = rep - .all_task_uuids()? - .drain(..) - // SAFETY: - // - value is not allocated and need not be freed - .map(|uuid| unsafe { TCUuid::return_val(uuid) }) - .collect(); - // SAFETY: - // - value will be freed (promised by caller) - Ok(unsafe { TCUuidList::return_val(uuids) }) - }, - TCUuidList::null_value(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the current working set for this replica. The resulting value must be freed -/// with tc_working_set_free. -/// -/// Returns NULL on error. -/// -/// ```c -/// EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet { - wrap( - rep, - |rep| { - let ws = rep.working_set()?; - // SAFETY: - // - caller promises to free this value - Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get an existing task by its UUID. -/// -/// Returns NULL when the task does not exist, and on error. Consult tc_replica_error -/// to distinguish the two conditions. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask { - wrap( - rep, - |rep| { - // SAFETY: - // - tcuuid is a valid TCUuid (all bytes are valid) - // - tcuuid is Copy so ownership doesn't matter - let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - if let Some(task) = rep.get_task(uuid)? { - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - } else { - Ok(std::ptr::null_mut()) - } - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Create a new task. The task must not already exist. -/// -/// Returns the task, or NULL on error. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, -/// enum TCStatus status, -/// struct TCString description); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_new_task( - rep: *mut TCReplica, - status: TCStatus, - description: TCString, -) -> *mut TCTask { - // SAFETY: - // - description is valid (promised by caller) - // - caller will not use description after this call (convention) - let mut description = unsafe { TCString::val_from_arg(description) }; - wrap( - rep, - |rep| { - let task = rep.new_task(status.into(), description.as_str()?.to_string())?; - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Create a new task. The task must not already exist. -/// -/// Returns the task, or NULL on error. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_import_task_with_uuid( - rep: *mut TCReplica, - tcuuid: TCUuid, -) -> *mut TCTask { - wrap( - rep, - |rep| { - // SAFETY: - // - tcuuid is a valid TCUuid (all bytes are valid) - // - tcuuid is Copy so ownership doesn't matter - let uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - let task = rep.import_task_with_uuid(uuid)?; - // SAFETY: - // - caller promises to free this task - Ok(unsafe { TCTask::from(task).return_ptr() }) - }, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Synchronize this replica with a server. -/// -/// The `server` argument remains owned by the caller, and must be freed explicitly. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_sync( - rep: *mut TCReplica, - server: *mut TCServer, - avoid_snapshots: bool, -) -> TCResult { - wrap( - rep, - |rep| { - debug_assert!(!server.is_null()); - // SAFETY: - // - server is not NULL - // - *server is a valid TCServer (promised by caller) - // - server is valid for the lifetime of tc_replica_sync (not threadsafe) - // - server will not be accessed simultaneously (not threadsafe) - let server = unsafe { TCServer::from_ptr_arg_ref_mut(server) }; - rep.sync(server.as_mut(), avoid_snapshots)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Return undo local operations until the most recent UndoPoint. -/// -/// ```c -/// EXTERN_C TCReplicaOpList tc_replica_get_undo_ops(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_get_undo_ops(rep: *mut TCReplica) -> TCReplicaOpList { - wrap( - rep, - |rep| { - // SAFETY: - // - caller will free this value, either with tc_replica_commit_undo_ops or - // tc_replica_op_list_free. - Ok(unsafe { - TCReplicaOpList::return_val( - rep.get_undo_ops()? - .into_iter() - .map(TCReplicaOp::from) - .collect(), - ) - }) - }, - TCReplicaOpList::default(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Undo local operations in storage. -/// -/// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if -/// there are no operations that can be done. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_commit_undo_ops(struct TCReplica *rep, TCReplicaOpList tc_undo_ops, int32_t *undone_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_commit_undo_ops( - rep: *mut TCReplica, - tc_undo_ops: TCReplicaOpList, - undone_out: *mut i32, -) -> TCResult { - wrap( - rep, - |rep| { - // SAFETY: - // - `tc_undo_ops` is a valid value, as it was acquired from `tc_replica_get_undo_ops`. - let undo_ops: Vec = unsafe { TCReplicaOpList::val_from_arg(tc_undo_ops) } - .into_iter() - .map(|op| *op.inner) - .collect(); - let undone = i32::from(rep.commit_undo_ops(undo_ops)?); - if !undone_out.is_null() { - // SAFETY: - // - undone_out is not NULL (just checked) - // - undone_out is properly aligned (implicitly promised by caller) - unsafe { *undone_out = undone }; - } - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the number of local, un-synchronized operations (not including undo points), or -1 on -/// error. -/// -/// ```c -/// EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_num_local_operations(rep: *mut TCReplica) -> i64 { - wrap( - rep, - |rep| { - let count = rep.num_local_operations()? as i64; - Ok(count) - }, - -1, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the number of undo points (number of undo calls possible), or -1 on error. -/// -/// ```c -/// EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_num_undo_points(rep: *mut TCReplica) -> i64 { - wrap( - rep, - |rep| { - let count = rep.num_undo_points()? as i64; - Ok(count) - }, - -1, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically -/// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already -/// been created by this Replica, and may be useful when a Replica instance is held for a long time -/// and used to apply more than one user-visible change. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult { - wrap( - rep, - |rep| { - rep.add_undo_point(force)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` -/// is true, then existing tasks may be moved to new working-set indices; in any case, on -/// completion all pending tasks are in the working set and all non- pending tasks are not. -/// -/// ```c -/// EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_rebuild_working_set( - rep: *mut TCReplica, - renumber: bool, -) -> TCResult { - wrap( - rep, - |rep| { - rep.rebuild_working_set(renumber)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 902)] -/// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent -/// calls to this function will return NULL. The rep pointer must not be NULL. The caller must -/// free the returned string. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString { - // SAFETY: - // - rep is not NULL (promised by caller) - // - *rep is a valid TCReplica (promised by caller) - // - rep is valid for the duration of this function - // - rep is not modified by anything else (not threadsafe) - let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) }; - if let Some(rstring) = rep.error.take() { - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(rstring) } - } else { - TCString::default() - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Free a replica. The replica may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_replica_free(struct TCReplica *rep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) { - // SAFETY: - // - replica is not NULL (promised by caller) - // - replica is valid (promised by caller) - // - caller will not use description after this call (promised by caller) - let replica = unsafe { TCReplica::take_from_ptr_arg(rep) }; - if replica.mut_borrowed { - panic!("replica is borrowed and cannot be freed"); - } - drop(replica); -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Free a vector of ReplicaOp. The vector may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_replica_op_list_free(struct TCReplicaOpList *oplist); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_list_free(oplist: *mut TCReplicaOpList) { - debug_assert!(!oplist.is_null()); - // SAFETY: - // - arg is not NULL (just checked) - // - `*oplist` is valid (guaranteed by caller not double-freeing this value) - unsafe { - TCReplicaOpList::take_val_from_arg( - oplist, - // SAFETY: - // - value is empty, so the caller need not free it. - TCReplicaOpList::return_val(Vec::new()), - ) - }; -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return uuid field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_uuid(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_uuid(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Create { uuid } - | ReplicaOp::Delete { uuid, .. } - | ReplicaOp::Update { uuid, .. } = rop - { - let uuid_rstr: RustString = uuid.to_string().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(uuid_rstr) } - } else { - panic!("Operation has no uuid: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return property field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_property(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_property(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { property, .. } = rop { - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(property.clone().into()) } - } else { - panic!("Operation has no property: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return value field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_value(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_value(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { value, .. } = rop { - let value_rstr: RustString = value.clone().unwrap_or(String::new()).into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(value_rstr) } - } else { - panic!("Operation has no value: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return old value field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_old_value(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_old_value(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { old_value, .. } = rop { - let old_value_rstr: RustString = old_value.clone().unwrap_or(String::new()).into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(old_value_rstr) } - } else { - panic!("Operation has no old value: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return timestamp field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_timestamp(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_timestamp(op: *const TCReplicaOp) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Update { timestamp, .. } = rop { - let timestamp_rstr: RustString = timestamp.to_string().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(timestamp_rstr) } - } else { - panic!("Operation has no timestamp: {:#?}", rop); - } -} - -#[ffizz_header::item] -#[ffizz(order = 903)] -/// Return description field of old task field of ReplicaOp. -/// -/// ```c -/// EXTERN_C struct TCString tc_replica_op_get_old_task_description(struct TCReplicaOp *op); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_replica_op_get_old_task_description( - op: *const TCReplicaOp, -) -> TCString { - // SAFETY: - // - inner is not null - // - inner is a living object - let rop: &ReplicaOp = unsafe { (*op).inner.as_ref() }; - - if let ReplicaOp::Delete { old_task, .. } = rop { - let description_rstr: RustString = old_task["description"].clone().into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(description_rstr) } - } else { - panic!("Operation has no timestamp: {:#?}", rop); - } -} diff --git a/taskchampion/lib/src/result.rs b/taskchampion/lib/src/result.rs deleted file mode 100644 index bb72efb1d..000000000 --- a/taskchampion/lib/src/result.rs +++ /dev/null @@ -1,25 +0,0 @@ -#[ffizz_header::item] -#[ffizz(order = 100)] -/// ***** TCResult ***** -/// -/// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, -/// the associated object's `tc_.._error` method will return an error message. -/// -/// ```c -/// enum TCResult -/// #ifdef __cplusplus -/// : int32_t -/// #endif // __cplusplus -/// { -/// TC_RESULT_ERROR = -1, -/// TC_RESULT_OK = 0, -/// }; -/// #ifndef __cplusplus -/// typedef int32_t TCResult; -/// #endif // __cplusplus -/// ``` -#[repr(i32)] -pub enum TCResult { - Error = -1, - Ok = 0, -} diff --git a/taskchampion/lib/src/server.rs b/taskchampion/lib/src/server.rs deleted file mode 100644 index 47b79debc..000000000 --- a/taskchampion/lib/src/server.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use taskchampion::{Server, ServerConfig}; - -#[ffizz_header::item] -#[ffizz(order = 800)] -/// ***** TCServer ***** -/// -/// TCServer represents an interface to a sync server. Aside from new and free, a server -/// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. -/// -/// ## Safety -/// -/// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. -/// -/// ```c -/// typedef struct TCServer TCServer; -/// ``` -pub struct TCServer(Box); - -impl PassByPointer for TCServer {} - -impl From> for TCServer { - fn from(server: Box) -> TCServer { - TCServer(server) - } -} - -impl AsMut> for TCServer { - fn as_mut(&mut self) -> &mut Box { - &mut self.0 - } -} - -/// Utility function to allow using `?` notation to return an error value. -fn wrap(f: F, error_out: *mut TCString, err_value: T) -> T -where - F: FnOnce() -> anyhow::Result, -{ - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { *error_out = TCString::default() }; - } - - match f() { - Ok(v) => v, - Err(e) => { - if !error_out.is_null() { - // SAFETY: - // - error_out is not NULL (just checked) - // - properly aligned and valid (promised by caller) - unsafe { - TCString::val_to_arg_out(err_to_ruststring(e), error_out); - } - } - err_value - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 801)] -/// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the -/// description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_local( - server_dir: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - server_dir is valid (promised by caller) - // - caller will not use server_dir after this call (convention) - let mut server_dir = unsafe { TCString::val_from_arg(server_dir) }; - let server_config = ServerConfig::Local { - server_dir: server_dir.to_path_buf_mut()?, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 801)] -/// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the -/// description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_sync(struct TCString origin, -/// struct TCUuid client_id, -/// struct TCString encryption_secret, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_sync( - origin: TCString, - client_id: TCUuid, - encryption_secret: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - origin is valid (promised by caller) - // - origin ownership is transferred to this function - let origin = unsafe { TCString::val_from_arg(origin) }.into_string()?; - - // SAFETY: - // - client_id is a valid Uuid (any 8-byte sequence counts) - let client_id = unsafe { TCUuid::val_from_arg(client_id) }; - - // SAFETY: - // - encryption_secret is valid (promised by caller) - // - encryption_secret ownership is transferred to this function - let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) } - .as_bytes() - .to_vec(); - - let server_config = ServerConfig::Remote { - origin, - client_id, - encryption_secret, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 802)] -/// Create a new TCServer that connects to the Google Cloud Platform. See the TaskChampion docs -/// for the description of the arguments. -/// -/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -/// returned. The caller must free this string. -/// -/// The server must be freed after it is used - tc_replica_sync does not automatically free it. -/// -/// ```c -/// EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket, -/// struct TCString credential_path, -/// struct TCString encryption_secret, -/// struct TCString *error_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_new_gcp( - bucket: TCString, - credential_path_argument: TCString, - encryption_secret: TCString, - error_out: *mut TCString, -) -> *mut TCServer { - wrap( - || { - // SAFETY: - // - bucket is valid (promised by caller) - // - bucket ownership is transferred to this function - let bucket = unsafe { TCString::val_from_arg(bucket) }.into_string()?; - - // SAFETY: - // - credential_path is valid (promised by caller) - // - credential_path ownership is transferred to this function - - let credential_path = - unsafe { TCString::val_from_arg(credential_path_argument) }.into_string()?; - let credential_path = if credential_path.is_empty() { - None - } else { - Some(credential_path) - }; - - // SAFETY: - // - encryption_secret is valid (promised by caller) - // - encryption_secret ownership is transferred to this function - let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) } - .as_bytes() - .to_vec(); - let server_config = ServerConfig::Gcp { - bucket, - credential_path, - encryption_secret, - }; - let server = server_config.into_server()?; - // SAFETY: caller promises to free this server. - Ok(unsafe { TCServer::return_ptr(server.into()) }) - }, - error_out, - std::ptr::null_mut(), - ) -} - -#[ffizz_header::item] -#[ffizz(order = 899)] -/// Free a server. The server may not be used after this function returns and must not be freed -/// more than once. -/// -/// ```c -/// EXTERN_C void tc_server_free(struct TCServer *server); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) { - debug_assert!(!server.is_null()); - // SAFETY: - // - server is not NULL - // - server came from tc_server_new_.., which used return_ptr - // - server will not be used after (promised by caller) - let server = unsafe { TCServer::take_from_ptr_arg(server) }; - drop(server); -} diff --git a/taskchampion/lib/src/status.rs b/taskchampion/lib/src/status.rs deleted file mode 100644 index e0d370136..000000000 --- a/taskchampion/lib/src/status.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub use taskchampion::Status; - -#[ffizz_header::item] -#[ffizz(order = 700)] -/// ***** TCStatus ***** -/// -/// The status of a task, as defined by the task data model. -/// -/// ```c -/// #ifdef __cplusplus -/// typedef enum TCStatus : int32_t { -/// #else // __cplusplus -/// typedef int32_t TCStatus; -/// enum TCStatus { -/// #endif // __cplusplus -/// TC_STATUS_PENDING = 0, -/// TC_STATUS_COMPLETED = 1, -/// TC_STATUS_DELETED = 2, -/// TC_STATUS_RECURRING = 3, -/// // Unknown signifies a status in the task DB that was not -/// // recognized. -/// TC_STATUS_UNKNOWN = -1, -/// #ifdef __cplusplus -/// } TCStatus; -/// #else // __cplusplus -/// }; -/// #endif // __cplusplus -/// ``` -#[repr(i32)] -pub enum TCStatus { - Pending = 0, - Completed = 1, - Deleted = 2, - Recurring = 3, - Unknown = -1, -} - -impl From for Status { - fn from(status: TCStatus) -> Status { - match status { - TCStatus::Pending => Status::Pending, - TCStatus::Completed => Status::Completed, - TCStatus::Deleted => Status::Deleted, - TCStatus::Recurring => Status::Recurring, - _ => Status::Unknown(format!("unknown TCStatus {}", status as u32)), - } - } -} - -impl From for TCStatus { - fn from(status: Status) -> TCStatus { - match status { - Status::Pending => TCStatus::Pending, - Status::Completed => TCStatus::Completed, - Status::Deleted => TCStatus::Deleted, - Status::Recurring => TCStatus::Recurring, - Status::Unknown(_) => TCStatus::Unknown, - } - } -} diff --git a/taskchampion/lib/src/string.rs b/taskchampion/lib/src/string.rs deleted file mode 100644 index 01036c999..000000000 --- a/taskchampion/lib/src/string.rs +++ /dev/null @@ -1,773 +0,0 @@ -use crate::traits::*; -use crate::util::{string_into_raw_parts, vec_into_raw_parts}; -use std::ffi::{CStr, CString, OsString}; -use std::os::raw::c_char; -use std::path::PathBuf; - -#[ffizz_header::item] -#[ffizz(order = 200)] -/// ***** TCString ***** -/// -/// TCString supports passing strings into and out of the TaskChampion API. -/// -/// # Rust Strings and C Strings -/// -/// A Rust string can contain embedded NUL characters, while C considers such a character to mark -/// the end of a string. Strings containing embedded NULs cannot be represented as a "C string" -/// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In -/// general, these two functions should be used for handling arbitrary data, while more convenient -/// forms may be used where embedded NUL characters are impossible, such as in static strings. -/// -/// # UTF-8 -/// -/// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given -/// a `*TCString` containing invalid UTF-8. -/// -/// # Safety -/// -/// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All -/// other fields in a TCString are private and must not be used from C. They exist in the struct -/// to ensure proper allocation and alignment. -/// -/// When a `TCString` appears as a return value or output argument, ownership is passed to the -/// caller. The caller must pass that ownership back to another function or free the string. -/// -/// Any function taking a `TCString` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; and -/// - the memory referenced by the pointer must never be modified by C code. -/// -/// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is -/// given as a function argument, and the caller must not use or free TCStrings after passing them -/// to such API functions. -/// -/// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail -/// for such a value. -/// -/// TCString is not threadsafe. -/// -/// ```c -/// typedef struct TCString { -/// void *ptr; // opaque, but may be checked for NULL -/// size_t _u1; // reserved -/// size_t _u2; // reserved -/// uint8_t _u3; // reserved -/// } TCString; -/// ``` -#[repr(C)] -#[derive(Debug)] -pub struct TCString { - // defined based on the type - ptr: *mut libc::c_void, - len: usize, - cap: usize, - - // type of TCString this represents - ty: u8, -} - -// TODO: figure out how to ignore this but still use it in TCString -/// A discriminator for TCString -#[repr(u8)] -enum TCStringType { - /// Null. Nothing is contained in this string. - /// - /// * `ptr` is NULL. - /// * `len` and `cap` are zero. - Null = 0, - - /// A CString. - /// - /// * `ptr` is the result of CString::into_raw, containing a terminating NUL. It may not be - /// valid UTF-8. - /// * `len` and `cap` are zero. - CString, - - /// A CStr, referencing memory borrowed from C - /// - /// * `ptr` points to the string, containing a terminating NUL. It may not be valid UTF-8. - /// * `len` and `cap` are zero. - CStr, - - /// A String. - /// - /// * `ptr`, `len`, and `cap` are as would be returned from String::into_raw_parts. - String, - - /// A byte sequence. - /// - /// * `ptr`, `len`, and `cap` are as would be returned from Vec::into_raw_parts. - Bytes, -} - -impl Default for TCString { - fn default() -> Self { - TCString { - ptr: std::ptr::null_mut(), - len: 0, - cap: 0, - ty: TCStringType::Null as u8, - } - } -} - -impl TCString { - pub(crate) fn is_null(&self) -> bool { - self.ptr.is_null() - } -} - -#[derive(PartialEq, Eq, Debug, Default)] -pub enum RustString<'a> { - #[default] - Null, - CString(CString), - CStr(&'a CStr), - String(String), - Bytes(Vec), -} - -impl PassByValue for TCString { - type RustType = RustString<'static>; - - unsafe fn from_ctype(self) -> Self::RustType { - match self.ty { - ty if ty == TCStringType::CString as u8 => { - // SAFETY: - // - ptr was derived from CString::into_raw - // - data was not modified since that time (caller promises) - RustString::CString(unsafe { CString::from_raw(self.ptr as *mut c_char) }) - } - ty if ty == TCStringType::CStr as u8 => { - // SAFETY: - // - ptr was created by CStr::as_ptr - // - data was not modified since that time (caller promises) - RustString::CStr(unsafe { CStr::from_ptr(self.ptr as *mut c_char) }) - } - ty if ty == TCStringType::String as u8 => { - // SAFETY: - // - ptr was created by string_into_raw_parts - // - data was not modified since that time (caller promises) - RustString::String(unsafe { - String::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) - }) - } - ty if ty == TCStringType::Bytes as u8 => { - // SAFETY: - // - ptr was created by vec_into_raw_parts - // - data was not modified since that time (caller promises) - RustString::Bytes(unsafe { - Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap) - }) - } - _ => RustString::Null, - } - } - - fn as_ctype(arg: Self::RustType) -> Self { - match arg { - RustString::Null => Self { - ty: TCStringType::Null as u8, - ..Default::default() - }, - RustString::CString(cstring) => Self { - ty: TCStringType::CString as u8, - ptr: cstring.into_raw() as *mut libc::c_void, - ..Default::default() - }, - RustString::CStr(cstr) => Self { - ty: TCStringType::CStr as u8, - ptr: cstr.as_ptr() as *mut libc::c_void, - ..Default::default() - }, - RustString::String(string) => { - let (ptr, len, cap) = string_into_raw_parts(string); - Self { - ty: TCStringType::String as u8, - ptr: ptr as *mut libc::c_void, - len, - cap, - } - } - RustString::Bytes(bytes) => { - let (ptr, len, cap) = vec_into_raw_parts(bytes); - Self { - ty: TCStringType::Bytes as u8, - ptr: ptr as *mut libc::c_void, - len, - cap, - } - } - } - } -} - -impl<'a> RustString<'a> { - /// Get a regular Rust &str for this value. - pub(crate) fn as_str(&mut self) -> Result<&str, std::str::Utf8Error> { - match self { - RustString::CString(cstring) => cstring.as_c_str().to_str(), - RustString::CStr(cstr) => cstr.to_str(), - RustString::String(ref string) => Ok(string.as_ref()), - RustString::Bytes(_) => { - self.bytes_to_string()?; - self.as_str() // now the String variant, so won't recurse - } - RustString::Null => unreachable!(), - } - } - - /// Consume this RustString and return an equivalent String, or an error if not - /// valid UTF-8. In the error condition, the original data is lost. - pub(crate) fn into_string(mut self) -> Result { - match self { - RustString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()), - RustString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()), - RustString::String(string) => Ok(string), - RustString::Bytes(_) => { - self.bytes_to_string()?; - self.into_string() // now the String variant, so won't recurse - } - RustString::Null => unreachable!(), - } - } - - pub(crate) fn as_bytes(&self) -> &[u8] { - match self { - RustString::CString(cstring) => cstring.as_bytes(), - RustString::CStr(cstr) => cstr.to_bytes(), - RustString::String(string) => string.as_bytes(), - RustString::Bytes(bytes) => bytes.as_ref(), - RustString::Null => unreachable!(), - } - } - - /// Convert the RustString, in place, from the Bytes to String variant. On successful return, - /// the RustString has variant RustString::String. - fn bytes_to_string(&mut self) -> Result<(), std::str::Utf8Error> { - let mut owned = RustString::Null; - // temporarily swap a Null value into self; we'll swap that back - // shortly. - std::mem::swap(self, &mut owned); - match owned { - RustString::Bytes(bytes) => match String::from_utf8(bytes) { - Ok(string) => { - *self = RustString::String(string); - Ok(()) - } - Err(e) => { - let (e, bytes) = (e.utf8_error(), e.into_bytes()); - // put self back as we found it - *self = RustString::Bytes(bytes); - Err(e) - } - }, - _ => { - // not bytes, so just swap back - std::mem::swap(self, &mut owned); - Ok(()) - } - } - } - - /// Convert the RustString, in place, into one of the C variants. If this is not - /// possible, such as if the string contains an embedded NUL, then the string - /// remains unchanged. - fn string_to_cstring(&mut self) { - let mut owned = RustString::Null; - // temporarily swap a Null value into self; we'll swap that back shortly - std::mem::swap(self, &mut owned); - match owned { - RustString::String(string) => { - match CString::new(string) { - Ok(cstring) => { - *self = RustString::CString(cstring); - } - Err(nul_err) => { - // recover the underlying String from the NulError and restore - // the RustString - let original_bytes = nul_err.into_vec(); - // SAFETY: original_bytes came from a String moments ago, so still valid utf8 - let string = unsafe { String::from_utf8_unchecked(original_bytes) }; - *self = RustString::String(string); - } - } - } - _ => { - // not a CString, so just swap back - std::mem::swap(self, &mut owned); - } - } - } - - pub(crate) fn to_path_buf_mut(&mut self) -> Result { - #[cfg(unix)] - let path: OsString = { - // on UNIX, we can use the bytes directly, without requiring that they - // be valid UTF-8. - use std::ffi::OsStr; - use std::os::unix::ffi::OsStrExt; - OsStr::from_bytes(self.as_bytes()).to_os_string() - }; - #[cfg(windows)] - let path: OsString = { - // on Windows, we assume the filename is valid Unicode, so it can be - // represented as UTF-8. - OsString::from(self.as_str()?.to_string()) - }; - Ok(path.into()) - } -} - -impl<'a> From for RustString<'a> { - fn from(string: String) -> RustString<'a> { - RustString::String(string) - } -} - -impl From<&str> for RustString<'static> { - fn from(string: &str) -> RustString<'static> { - RustString::String(string.to_string()) - } -} - -/// Utility function to borrow a TCString from a pointer arg, modify it, -/// and restore it. -/// -/// This implements a kind of "interior mutability", relying on the -/// single-threaded use of all TC* types. -/// -/// # SAFETY -/// -/// - tcstring must not be NULL -/// - *tcstring must be a valid TCString -/// - *tcstring must not be accessed by anything else, despite the *const -unsafe fn wrap(tcstring: *const TCString, f: F) -> T -where - F: FnOnce(&mut RustString) -> T, -{ - debug_assert!(!tcstring.is_null()); - - // SAFETY: - // - we have exclusive to *tcstring (promised by caller) - let tcstring = tcstring as *mut TCString; - - // SAFETY: - // - tcstring is not NULL - // - *tcstring is a valid string (promised by caller) - let mut rstring = unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }; - - let rv = f(&mut rstring); - - // update the caller's TCString with the updated RustString - // SAFETY: - // - tcstring is not NULL (we just took from it) - // - tcstring points to valid memory (we just took from it) - unsafe { TCString::val_to_arg_out(rstring, tcstring) }; - - rv -} - -#[ffizz_header::item] -#[ffizz(order = 210)] -/// ***** TCStringList ***** -/// -/// TCStringList represents a list of strings. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCStringList { -/// // number of strings in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // TCStringList representing each string. These remain owned by the TCStringList instance and will -/// // be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the -/// // *TCStringList at indexes 0..len-1 are not NULL. -/// struct TCString *items; -/// } TCStringList; -/// ``` -#[repr(C)] -pub struct TCStringList { - len: libc::size_t, - /// total size of items (internal use only) - capacity: libc::size_t, - items: *mut TCString, -} - -impl CList for TCStringList { - type Element = TCString; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCStringList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString referencing the given C string. The C string must remain valid and -/// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a -/// static string. -/// -/// NOTE: this function does _not_ take responsibility for freeing the given C string. The -/// given string can be freed once the TCString referencing it has been freed. -/// -/// For example: -/// -/// ```text -/// char *url = get_item_url(..); // dynamically allocate C string -/// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed -/// free(url); // string is no longer referenced and can be freed -/// ``` -/// -/// ```c -/// EXTERN_C struct TCString tc_string_borrow(const char *cstr); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString { - debug_assert!(!cstr.is_null()); - // SAFETY: - // - cstr is not NULL (promised by caller, verified by assertion) - // - cstr's lifetime exceeds that of the TCString (promised by caller) - // - cstr contains a valid NUL terminator (promised by caller) - // - cstr's content will not change before it is destroyed (promised by caller) - let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::CStr(cstr)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString by cloning the content of the given C string. The resulting TCString -/// is independent of the given string, which can be freed or overwritten immediately. -/// -/// ```c -/// EXTERN_C struct TCString tc_string_clone(const char *cstr); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString { - debug_assert!(!cstr.is_null()); - // SAFETY: - // - cstr is not NULL (promised by caller, verified by assertion) - // - cstr's lifetime exceeds that of this function (by C convention) - // - cstr contains a valid NUL terminator (promised by caller) - // - cstr's content will not change before it is destroyed (by C convention) - let cstr: &CStr = unsafe { CStr::from_ptr(cstr) }; - let cstring: CString = cstr.into(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::CString(cstring)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Create a new TCString containing the given string with the given length. This allows creation -/// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting -/// TCString is independent of the passed buffer, which may be reused or freed immediately. -/// -/// The length should _not_ include any trailing NUL. -/// -/// The given length must be less than half the maximum value of usize. -/// -/// ```c -/// EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_clone_with_len( - buf: *const libc::c_char, - len: usize, -) -> TCString { - debug_assert!(!buf.is_null()); - debug_assert!(len < isize::MAX as usize); - // SAFETY: - // - buf is valid for len bytes (by C convention) - // - (no alignment requirements for a byte slice) - // - content of buf will not be mutated during the lifetime of this slice (lifetime - // does not outlive this function call) - // - the length of the buffer is less than isize::MAX (promised by caller) - let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) }; - - // allocate and copy into Rust-controlled memory - let vec = slice.to_vec(); - - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(RustString::Bytes(vec)) } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Get the content of the string as a regular C string. The given string must be valid. The -/// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The -/// returned C string is valid until the TCString is freed or passed to another TC API function. -/// -/// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is -/// valid and NUL-free. -/// -/// This function takes the TCString by pointer because it may be modified in-place to add a NUL -/// terminator. The pointer must not be NULL. -/// -/// This function does _not_ take ownership of the TCString. -/// -/// ```c -/// EXTERN_C const char *tc_string_content(const struct TCString *tcstring); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char { - // SAFETY; - // - tcstring is not NULL (promised by caller) - // - *tcstring is valid (promised by caller) - // - *tcstring is not accessed concurrently (single-threaded) - unsafe { - wrap(tcstring, |rstring| { - // try to eliminate the Bytes variant. If this fails, we'll return NULL - // below, so the error is ignorable. - let _ = rstring.bytes_to_string(); - - // and eliminate the String variant - rstring.string_to_cstring(); - - match &rstring { - RustString::CString(cstring) => cstring.as_ptr(), - RustString::String(_) => std::ptr::null(), // string_to_cstring failed - RustString::CStr(cstr) => cstr.as_ptr(), - RustString::Bytes(_) => std::ptr::null(), // already returned above - RustString::Null => unreachable!(), - } - }) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Get the content of the string as a pointer and length. The given string must not be NULL. -/// This function can return any string, even one including NUL bytes or invalid UTF-8. The -/// returned buffer is valid until the TCString is freed or passed to another TaskChampio -/// function. -/// -/// This function takes the TCString by pointer because it may be modified in-place to add a NUL -/// terminator. The pointer must not be NULL. -/// -/// This function does _not_ take ownership of the TCString. -/// -/// ```c -/// EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_content_with_len( - tcstring: *const TCString, - len_out: *mut usize, -) -> *const libc::c_char { - // SAFETY; - // - tcstring is not NULL (promised by caller) - // - *tcstring is valid (promised by caller) - // - *tcstring is not accessed concurrently (single-threaded) - unsafe { - wrap(tcstring, |rstring| { - let bytes = rstring.as_bytes(); - - // SAFETY: - // - len_out is not NULL (promised by caller) - // - len_out points to valid memory (promised by caller) - // - len_out is properly aligned (C convention) - usize::val_to_arg_out(bytes.len(), len_out); - bytes.as_ptr() as *const libc::c_char - }) - } -} - -#[ffizz_header::item] -#[ffizz(order = 201)] -/// Free a TCString. The given string must not be NULL. The string must not be used -/// after this function returns, and must not be freed more than once. -/// -/// ```c -/// EXTERN_C void tc_string_free(struct TCString *tcstring); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) { - // SAFETY: - // - tcstring is not NULL (promised by caller) - // - caller is exclusive owner of tcstring (promised by caller) - drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) }); -} - -#[ffizz_header::item] -#[ffizz(order = 211)] -/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -/// -/// ```c -/// EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) { - // SAFETY: - // - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcstrings) }; -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; - assert!(!tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcstrings = unsafe { TCStringList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_string_list_free(&mut tcstrings) }; - assert!(tcstrings.items.is_null()); - assert_eq!(tcstrings.len, 0); - assert_eq!(tcstrings.capacity, 0); - } - - const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28"; - - fn make_cstring() -> RustString<'static> { - RustString::CString(CString::new("a string").unwrap()) - } - - fn make_cstr() -> RustString<'static> { - let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap(); - RustString::CStr(cstr) - } - - fn make_string() -> RustString<'static> { - RustString::String("a string".into()) - } - - fn make_string_with_nul() -> RustString<'static> { - RustString::String("a \0 nul!".into()) - } - - fn make_invalid_bytes() -> RustString<'static> { - RustString::Bytes(INVALID_UTF8.to_vec()) - } - - fn make_bytes() -> RustString<'static> { - RustString::Bytes(b"bytes".to_vec()) - } - - #[test] - fn cstring_as_str() { - assert_eq!(make_cstring().as_str().unwrap(), "a string"); - } - - #[test] - fn cstr_as_str() { - assert_eq!(make_cstr().as_str().unwrap(), "a string"); - } - - #[test] - fn string_as_str() { - assert_eq!(make_string().as_str().unwrap(), "a string"); - } - - #[test] - fn string_with_nul_as_str() { - assert_eq!(make_string_with_nul().as_str().unwrap(), "a \0 nul!"); - } - - #[test] - fn invalid_bytes_as_str() { - let as_str_err = make_invalid_bytes().as_str().unwrap_err(); - assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid - } - - #[test] - fn valid_bytes_as_str() { - assert_eq!(make_bytes().as_str().unwrap(), "bytes"); - } - - #[test] - fn cstring_as_bytes() { - assert_eq!(make_cstring().as_bytes(), b"a string"); - } - - #[test] - fn cstr_as_bytes() { - assert_eq!(make_cstr().as_bytes(), b"a string"); - } - - #[test] - fn string_as_bytes() { - assert_eq!(make_string().as_bytes(), b"a string"); - } - - #[test] - fn string_with_nul_as_bytes() { - assert_eq!(make_string_with_nul().as_bytes(), b"a \0 nul!"); - } - - #[test] - fn invalid_bytes_as_bytes() { - assert_eq!(make_invalid_bytes().as_bytes(), INVALID_UTF8); - } - - #[test] - fn cstring_string_to_cstring() { - let mut tcstring = make_cstring(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstring()); // unchanged - } - - #[test] - fn cstr_string_to_cstring() { - let mut tcstring = make_cstr(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstr()); // unchanged - } - - #[test] - fn string_string_to_cstring() { - let mut tcstring = make_string(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_cstring()); // converted to CString, same content - } - - #[test] - fn string_with_nul_string_to_cstring() { - let mut tcstring = make_string_with_nul(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_string_with_nul()); // unchanged - } - - #[test] - fn bytes_string_to_cstring() { - let mut tcstring = make_bytes(); - tcstring.string_to_cstring(); - assert_eq!(tcstring, make_bytes()); // unchanged - } -} diff --git a/taskchampion/lib/src/task.rs b/taskchampion/lib/src/task.rs deleted file mode 100644 index a5d2e80de..000000000 --- a/taskchampion/lib/src/task.rs +++ /dev/null @@ -1,1304 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use crate::util::err_to_ruststring; -use crate::TCKV; -use std::convert::TryFrom; -use std::ops::Deref; -use std::ptr::NonNull; -use std::str::FromStr; -use taskchampion::{utc_timestamp, Annotation, Tag, Task, TaskMut, Uuid}; - -#[ffizz_header::item] -#[ffizz(order = 1000)] -/// ***** TCTask ***** -/// -/// A task, as publicly exposed by this library. -/// -/// A task begins in "immutable" mode. It must be converted to "mutable" mode -/// to make any changes, and doing so requires exclusive access to the replica -/// until the task is freed or converted back to immutable mode. -/// -/// An immutable task carries no reference to the replica that created it, and can be used until it -/// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and -/// must be freed or made immutable before the replica is freed. -/// -/// All `tc_task_..` functions taking a task as an argument require that it not be NULL. -/// -/// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then -/// `tc_task_error` will return the error message. -/// -/// # Safety -/// -/// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, -/// with tc_task_list_free). -/// -/// Any function taking a `*TCTask` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from a tc_… function; -/// - the memory referenced by the pointer must never be modified by C code; and -/// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. -/// -/// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. -/// -/// TCTasks are not threadsafe. -/// -/// ```c -/// typedef struct TCTask TCTask; -/// ``` -pub struct TCTask { - /// The wrapped Task or TaskMut - inner: Inner, - - /// The error from the most recent operation, if any - error: Option>, -} - -enum Inner { - /// A regular, immutable task - Immutable(Task), - - /// A mutable task, together with the replica to which it holds an exclusive - /// reference. - Mutable(TaskMut<'static>, *mut TCReplica), - - /// A transitional state for a TCTask as it goes from mutable to immutable and back. A task - /// can only be in this state outside of [`to_mut`] and [`to_immut`] if a panic occurs during - /// one of those methods. - Invalid, -} - -impl PassByPointer for TCTask {} - -impl TCTask { - /// Make an immutable TCTask into a mutable TCTask. Does nothing if the task - /// is already mutable. - /// - /// # Safety - /// - /// The tcreplica pointer must not be NULL, and the replica it points to must not - /// be freed before TCTask.to_immut completes. - unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) { - self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { - Inner::Immutable(task) => { - // SAFETY: - // - tcreplica is not null (promised by caller) - // - tcreplica outlives the pointer in this variant (promised by caller) - let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; - let rep_ref = tcreplica_ref.borrow_mut(); - Inner::Mutable(task.into_mut(rep_ref), tcreplica) - } - Inner::Mutable(task, tcreplica) => Inner::Mutable(task, tcreplica), - Inner::Invalid => unreachable!(), - } - } - - /// Make an mutable TCTask into a immutable TCTask. Does nothing if the task - /// is already immutable. - #[allow(clippy::wrong_self_convention)] // to_immut_mut is not better! - fn to_immut(&mut self) { - self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) { - Inner::Immutable(task) => Inner::Immutable(task), - Inner::Mutable(task, tcreplica) => { - // SAFETY: - // - tcreplica is not null (promised by caller of to_mut, which created this - // variant) - // - tcreplica is still alive (promised by caller of to_mut) - let tcreplica_ref: &mut TCReplica = - unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) }; - tcreplica_ref.release_borrow(); - Inner::Immutable(task.into_immut()) - } - Inner::Invalid => unreachable!(), - } - } -} - -impl From for TCTask { - fn from(task: Task) -> TCTask { - TCTask { - inner: Inner::Immutable(task), - error: None, - } - } -} - -/// Utility function to get a shared reference to the underlying Task. All Task getters -/// are error-free, so this does not handle errors. -fn wrap(task: *mut TCTask, f: F) -> T -where - F: FnOnce(&Task) -> T, -{ - // SAFETY: - // - task is not null (promised by caller) - // - task outlives this function (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &Task = match &tctask.inner { - Inner::Immutable(t) => t, - Inner::Mutable(t, _) => t.deref(), - Inner::Invalid => unreachable!(), - }; - tctask.error = None; - f(task) -} - -/// Utility function to get a mutable reference to the underlying Task. The -/// TCTask must be mutable. The inner function may use `?` syntax to return an -/// error, which will be represented with the `err_value` returned to C. -fn wrap_mut(task: *mut TCTask, f: F, err_value: T) -> T -where - F: FnOnce(&mut TaskMut) -> anyhow::Result, -{ - // SAFETY: - // - task is not null (promised by caller) - // - task outlives this function (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - let task: &mut TaskMut = match tctask.inner { - Inner::Immutable(_) => panic!("Task is immutable"), - Inner::Mutable(ref mut t, _) => t, - Inner::Invalid => unreachable!(), - }; - tctask.error = None; - match f(task) { - Ok(rv) => rv, - Err(e) => { - tctask.error = Some(err_to_ruststring(e)); - err_value - } - } -} - -impl TryFrom> for Tag { - type Error = anyhow::Error; - - fn try_from(mut rstring: RustString) -> Result { - let tagstr = rstring.as_str()?; - Tag::from_str(tagstr) - } -} - -#[ffizz_header::item] -#[ffizz(order = 1010)] -/// ***** TCTaskList ***** -/// -/// TCTaskList represents a list of tasks. -/// -/// The content of this struct must be treated as read-only: no fields or anything they reference -/// should be modified directly by C code. -/// -/// When an item is taken from this list, its pointer in `items` is set to NULL. -/// -/// ```c -/// typedef struct TCTaskList { -/// // number of tasks in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of pointers representing each task. These remain owned by the TCTaskList instance and -/// // will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. -/// // Pointers in the array may be NULL after `tc_task_list_take`. -/// struct TCTask **items; -/// } TCTaskList; -/// ``` -#[repr(C)] -pub struct TCTaskList { - /// number of tasks in items - len: libc::size_t, - - /// total size of items (internal use only) - capacity: libc::size_t, - - /// Array of pointers representing each task. These remain owned by the TCTaskList instance and - /// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. - /// Pointers in the array may be NULL after `tc_task_list_take`. - items: *mut Option>, -} - -impl CList for TCTaskList { - type Element = Option>; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCTaskList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's UUID. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid { - wrap(task, |task| { - // SAFETY: - // - value is not allocated and need not be freed - unsafe { TCUuid::return_val(task.get_uuid()) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's status. -/// -/// ```c -/// EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus { - wrap(task, |task| task.get_status().into()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the underlying key/value pairs for this task. The returned TCKVList is -/// a "snapshot" of the task and will not be updated if the task is subsequently -/// modified. It is the caller's responsibility to free the TCKVList. -/// -/// ```c -/// EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList { - wrap(task, |task| { - let vec: Vec = task - .get_taskmap() - .iter() - .map(|(k, v)| { - let key = RustString::from(k.as_ref()); - let value = RustString::from(v.as_ref()); - TCKV::as_ctype((key, value)) - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCKVList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task's description. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString { - wrap(task, |task| { - let descr = task.get_description(); - // SAFETY: - // - caller promises to free this string - unsafe { TCString::return_val(descr.into()) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get a task property's value, or NULL if the task has no such property, (including if the -/// property name is not valid utf-8). -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_value(task: *mut TCTask, property: TCString) -> TCString { - // SAFETY: - // - property is valid (promised by caller) - // - caller will not use property after this call (convention) - let mut property = unsafe { TCString::val_from_arg(property) }; - wrap(task, |task| { - if let Ok(property) = property.as_str() { - let value = task.get_value(property); - if let Some(value) = value { - // SAFETY: - // - caller promises to free this string - return unsafe { TCString::return_val(value.into()) }; - } - } - TCString::default() // null value - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the entry timestamp for a task (when it was created), or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_entry(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_entry(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_entry())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the wait timestamp for a task, or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_wait(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_wait(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_wait())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the modified timestamp for a task, or 0 if not set. -/// -/// ```c -/// EXTERN_C time_t tc_task_get_modified(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_modified(task: *mut TCTask) -> libc::time_t { - wrap(task, |task| libc::time_t::as_ctype(task.get_modified())) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is waiting. -/// -/// ```c -/// EXTERN_C bool tc_task_is_waiting(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_waiting()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is active (started and not stopped). -/// -/// ```c -/// EXTERN_C bool tc_task_is_active(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_active()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is blocked (depends on at least one other task). -/// -/// ```c -/// EXTERN_C bool tc_task_is_blocked(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_blocked(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_blocked()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task is blocking (at least one other task depends on it). -/// -/// ```c -/// EXTERN_C bool tc_task_is_blocking(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_is_blocking(task: *mut TCTask) -> bool { - wrap(task, |task| task.is_blocking()) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Check if a task has the given tag. If the tag is invalid, this function will return false, as -/// that (invalid) tag is not present. No error will be reported via `tc_task_error`. -/// -/// ```c -/// EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap(task, |task| { - if let Ok(tag) = Tag::try_from(tcstring) { - task.has_tag(&tag) - } else { - false - } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the tags for the task. -/// -/// The caller must free the returned TCStringList instance. The TCStringList instance does not -/// reference the task and the two may be freed in any order. -/// -/// ```c -/// EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList { - wrap(task, |task| { - let vec: Vec = task - .get_tags() - .map(|t| { - // SAFETY: - // - this TCString will be freed via tc_string_list_free. - unsafe { TCString::return_val(t.as_ref().into()) } - }) - .collect(); - // SAFETY: - // - caller will free the list - unsafe { TCStringList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the annotations for the task. -/// -/// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not -/// reference the task and the two may be freed in any order. -/// -/// ```c -/// EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList { - wrap(task, |task| { - let vec: Vec = task - .get_annotations() - .map(|a| { - let description = RustString::from(a.description); - TCAnnotation::as_ctype((a.entry, description)) - }) - .collect(); - // SAFETY: - // - caller will free the list - unsafe { TCAnnotationList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the named UDA from the task. -/// -/// Returns a TCString with NULL ptr field if the UDA does not exist. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, -) -> TCString { - wrap(task, |task| { - // SAFETY: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - if let Ok(ns) = unsafe { TCString::val_from_arg(ns) }.as_str() { - // SAFETY: same - if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { - if let Some(value) = task.get_uda(ns, key) { - // SAFETY: - // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; - } - } - } - TCString::default() - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get the named legacy UDA from the task. -/// -/// Returns NULL if the UDA does not exist. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_uda(task: *mut TCTask, key: TCString) -> TCString { - wrap(task, |task| { - // SAFETY: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() { - if let Some(value) = task.get_legacy_uda(key) { - // SAFETY: - // - caller will free this string (caller promises) - return unsafe { TCString::return_val(value.into()) }; - } - } - TCString::default() - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all UDAs for this task. -/// -/// Legacy UDAs are represented with an empty string in the ns field. -/// -/// ```c -/// EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList { - wrap(task, |task| { - let vec: Vec = task - .get_udas() - .map(|((ns, key), value)| { - // SAFETY: - // - will be freed by tc_uda_list_free - unsafe { - TCUda::return_val(Uda { - ns: Some(ns.into()), - key: key.into(), - value: value.into(), - }) - } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUdaList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all UDAs for this task. -/// -/// All TCUdas in this list have a NULL ns field. The entire UDA key is -/// included in the key field. The caller must free the returned list. -/// -/// ```c -/// EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList { - wrap(task, |task| { - let vec: Vec = task - .get_legacy_udas() - .map(|(key, value)| { - // SAFETY: - // - will be freed by tc_uda_list_free - unsafe { - TCUda::return_val(Uda { - ns: None, - key: key.into(), - value: value.into(), - }) - } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUdaList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1001)] -/// Get all dependencies for a task. -/// -/// ```c -/// EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_get_dependencies(task: *mut TCTask) -> TCUuidList { - wrap(task, |task| { - let vec: Vec = task - .get_dependencies() - .map(|u| { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(u) } - }) - .collect(); - // SAFETY: - // - caller will free this list - unsafe { TCUuidList::return_val(vec) } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1002)] -/// Convert an immutable task into a mutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes mutable. -/// -/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ -/// until this task is made immutable again. This implies that it is not allowed for more than one -/// task associated with a replica to be mutable at any time. -/// -/// Typically mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: -/// -/// ```text -/// tc_task_to_mut(task, rep); -/// success = tc_task_done(task); -/// tc_task_to_immut(task, rep); -/// if (!success) { ... } -/// ``` -/// -/// ```c -/// EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - // SAFETY: - // - tcreplica is not NULL (promised by caller) - // - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller, - // who cannot call tc_replica_free during this time) - unsafe { tctask.to_mut(tcreplica) }; -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's status. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult { - wrap_mut( - task, - |task| { - task.set_status(status.into())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_value( - task: *mut TCTask, - property: TCString, - value: TCString, -) -> TCResult { - // SAFETY: - // - property is valid (promised by caller) - // - caller will not use property after this call (convention) - let mut property = unsafe { TCString::val_from_arg(property) }; - let value = if value.is_null() { - None - } else { - // SAFETY: - // - value is valid (promised by caller, after NULL check) - // - caller will not use value after this call (convention) - Some(unsafe { TCString::val_from_arg(value) }) - }; - wrap_mut( - task, - |task| { - let value_str = if let Some(mut v) = value { - Some(v.as_str()?.to_string()) - } else { - None - }; - task.set_value(property.as_str()?.to_string(), value_str)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's description. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_description( - task: *mut TCTask, - description: TCString, -) -> TCResult { - // SAFETY: - // - description is valid (promised by caller) - // - caller will not use description after this call (convention) - let mut description = unsafe { TCString::val_from_arg(description) }; - wrap_mut( - task, - |task| { - task.set_description(description.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's entry (creation time). Pass entry=0 to unset -/// the entry field. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult { - wrap_mut( - task, - |task| { - // SAFETY: any time_t value is a valid timestamp - task.set_entry(unsafe { entry.from_ctype() })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult { - wrap_mut( - task, - |task| { - // SAFETY: any time_t value is a valid timestamp - task.set_wait(unsafe { wait.from_ctype() })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a mutable task's modified timestamp. The value cannot be zero. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_modified( - task: *mut TCTask, - modified: libc::time_t, -) -> TCResult { - wrap_mut( - task, - |task| { - task.set_modified( - // SAFETY: any time_t value is a valid timestamp - unsafe { modified.from_ctype() } - .ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?, - )?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Start a task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_start(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.start()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Stop a task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_stop(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.stop()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Mark a task as done. -/// -/// ```c -/// EXTERN_C TCResult tc_task_done(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.done()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Mark a task as deleted. -/// -/// ```c -/// EXTERN_C TCResult tc_task_delete(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult { - wrap_mut( - task, - |task| { - task.delete()?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add a tag to a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap_mut( - task, - |task| { - let tag = Tag::try_from(tcstring)?; - task.add_tag(&tag)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a tag from a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult { - // SAFETY: - // - tag is valid (promised by caller) - // - caller will not use tag after this call (convention) - let tcstring = unsafe { TCString::val_from_arg(tag) }; - wrap_mut( - task, - |task| { - let tag = Tag::try_from(tcstring)?; - task.remove_tag(&tag)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add an annotation to a mutable task. This call takes ownership of the -/// passed annotation, which must not be used after the call returns. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_annotation( - task: *mut TCTask, - annotation: *mut TCAnnotation, -) -> TCResult { - // SAFETY: - // - annotation is not NULL (promised by caller) - // - annotation is return from a tc_string_.. so is valid - // - caller will not use annotation after this call - let (entry, description) = - unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) }; - wrap_mut( - task, - |task| { - let description = description.into_string()?; - task.add_annotation(Annotation { entry, description })?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove an annotation from a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult { - wrap_mut( - task, - |task| { - task.remove_annotation(utc_timestamp(entry))?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a UDA on a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, -/// struct TCString ns, -/// struct TCString key, -/// struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, - value: TCString, -) -> TCResult { - // safety: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - let mut ns = unsafe { TCString::val_from_arg(ns) }; - // SAFETY: same - let mut key = unsafe { TCString::val_from_arg(key) }; - // SAFETY: same - let mut value = unsafe { TCString::val_from_arg(value) }; - wrap_mut( - task, - |task| { - task.set_uda(ns.as_str()?, key.as_str()?, value.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a UDA fraom a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_uda( - task: *mut TCTask, - ns: TCString, - key: TCString, -) -> TCResult { - // safety: - // - ns is valid (promised by caller) - // - caller will not use ns after this call (convention) - let mut ns = unsafe { TCString::val_from_arg(ns) }; - // SAFETY: same - let mut key = unsafe { TCString::val_from_arg(key) }; - wrap_mut( - task, - |task| { - task.remove_uda(ns.as_str()?, key.as_str()?)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Set a legacy UDA on a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_set_legacy_uda( - task: *mut TCTask, - key: TCString, - value: TCString, -) -> TCResult { - // safety: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - let mut key = unsafe { TCString::val_from_arg(key) }; - // SAFETY: same - let mut value = unsafe { TCString::val_from_arg(value) }; - wrap_mut( - task, - |task| { - task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a UDA fraom a mutable task. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult { - // safety: - // - key is valid (promised by caller) - // - caller will not use key after this call (convention) - let mut key = unsafe { TCString::val_from_arg(key) }; - wrap_mut( - task, - |task| { - task.remove_legacy_uda(key.as_str()?.to_string())?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Add a dependency. -/// -/// ```c -/// EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_add_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; - wrap_mut( - task, - |task| { - task.add_dependency(dep)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1003)] -/// Remove a dependency. -/// -/// ```c -/// EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_remove_dependency(task: *mut TCTask, dep: TCUuid) -> TCResult { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let dep: Uuid = unsafe { TCUuid::val_from_arg(dep) }; - wrap_mut( - task, - |task| { - task.remove_dependency(dep)?; - Ok(TCResult::Ok) - }, - TCResult::Error, - ) -} - -#[ffizz_header::item] -#[ffizz(order = 1004)] -/// Convert a mutable task into an immutable task. -/// -/// The task must not be NULL. It is modified in-place, and becomes immutable. -/// -/// The replica passed to `tc_task_to_mut` may be used freely after this call. -/// -/// ```c -/// EXTERN_C void tc_task_to_immut(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - tctask.to_immut(); -} - -#[ffizz_header::item] -#[ffizz(order = 1005)] -/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. -/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The -/// caller must free the returned string. -/// -/// ```c -/// EXTERN_C struct TCString tc_task_error(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString { - // SAFETY: - // - task is not null (promised by caller) - // - task outlives 'a (promised by caller) - let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) }; - if let Some(rstring) = task.error.take() { - // SAFETY: - // - caller promises to free this value - unsafe { TCString::return_val(rstring) } - } else { - TCString::default() - } -} - -#[ffizz_header::item] -#[ffizz(order = 1006)] -/// Free a task. The given task must not be NULL. The task must not be used after this function -/// returns, and must not be freed more than once. -/// -/// If the task is currently mutable, it will first be made immutable. -/// -/// ```c -/// EXTERN_C void tc_task_free(struct TCTask *task); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) { - // SAFETY: - // - task is not NULL (promised by caller) - // - caller will not use the TCTask after this (promised by caller) - let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) }; - - // convert to immut if it was mutable - tctask.to_immut(); - - drop(tctask); -} - -#[ffizz_header::item] -#[ffizz(order = 1011)] -/// Take an item from a TCTaskList. After this call, the indexed item is no longer associated -/// with the list and becomes the caller's responsibility, just as if it had been returned from -/// `tc_replica_get_task`. -/// -/// The corresponding element in the `items` array will be set to NULL. If that field is already -/// NULL (that is, if the item has already been taken), this function will return NULL. If the -/// index is out of bounds, this function will also return NULL. -/// -/// The passed TCTaskList remains owned by the caller. -/// -/// ```c -/// EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_take(tasks: *mut TCTaskList, index: usize) -> *mut TCTask { - // SAFETY: - // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to - // modify the list directly, and tc_task_list_take leaves the list valid) - let p = unsafe { take_optional_pointer_list_item(tasks, index) }; - if let Some(p) = p { - p.as_ptr() - } else { - std::ptr::null_mut() - } -} - -#[ffizz_header::item] -#[ffizz(order = 1011)] -/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -/// -/// ```c -/// EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_task_list_free(tasks: *mut TCTaskList) { - // SAFETY: - // - tasks is not NULL and points to a valid TCTaskList (caller is not allowed to - // modify the list directly, and tc_task_list_take leaves the list valid) - // - caller promises not to use the value after return - unsafe { drop_optional_pointer_list(tasks) }; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tasks = unsafe { TCTaskList::return_val(Vec::new()) }; - assert!(!tasks.items.is_null()); - assert_eq!(tasks.len, 0); - assert_eq!(tasks.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tasks = unsafe { TCTaskList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_task_list_free(&mut tasks) }; - assert!(tasks.items.is_null()); - assert_eq!(tasks.len, 0); - assert_eq!(tasks.capacity, 0); - } -} diff --git a/taskchampion/lib/src/traits.rs b/taskchampion/lib/src/traits.rs deleted file mode 100644 index c9040071d..000000000 --- a/taskchampion/lib/src/traits.rs +++ /dev/null @@ -1,351 +0,0 @@ -use crate::util::vec_into_raw_parts; -use std::ptr::NonNull; - -/// Support for values passed to Rust by value. These are represented as full structs in C. Such -/// values are implicitly copyable, via C's struct assignment. -/// -/// The Rust and C types may differ, with from_ctype and as_ctype converting between them. -/// Implement this trait for the C type. -/// -/// The RustType must be droppable (not containing raw pointers). -pub(crate) trait PassByValue: Sized { - type RustType; - - /// Convert a C value to a Rust value. - /// - /// # Safety - /// - /// `self` must be a valid CType. - #[allow(clippy::wrong_self_convention)] - unsafe fn from_ctype(self) -> Self::RustType; - - /// Convert a Rust value to a C value. - fn as_ctype(arg: Self::RustType) -> Self; - - /// Take a value from C as an argument. - /// - /// # Safety - /// - /// - `self` must be a valid instance of the C type. This is typically ensured either by - /// requiring that C code not modify it, or by defining the valid values in C comments. - unsafe fn val_from_arg(arg: Self) -> Self::RustType { - // SAFETY: - // - arg is a valid CType (promised by caller) - unsafe { arg.from_ctype() } - } - - /// Take a value from C as a pointer argument, replacing it with the given value. This is used - /// to invalidate the C value as an additional assurance against subsequent use of the value. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid, properly aligned instance of the C type - unsafe fn take_val_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType { - // SAFETY: - // - arg is valid (promised by caller) - // - replacement is valid and aligned (guaranteed by Rust) - unsafe { std::ptr::swap(arg, &mut replacement) }; - // SAFETY: - // - replacement (formerly *arg) is a valid CType (promised by caller) - unsafe { PassByValue::val_from_arg(replacement) } - } - - /// Return a value to C - /// - /// # Safety - /// - /// - if the value is allocated, the caller must ensure that the value is eventually freed - unsafe fn return_val(arg: Self::RustType) -> Self { - Self::as_ctype(arg) - } - - /// Return a value to C, via an "output parameter" - /// - /// # Safety - /// - /// - `arg_out` must not be NULL and must be properly aligned and pointing to valid memory - /// of the size of CType. - unsafe fn val_to_arg_out(val: Self::RustType, arg_out: *mut Self) { - debug_assert!(!arg_out.is_null()); - // SAFETY: - // - arg_out is not NULL (promised by caller, asserted) - // - arg_out is properly aligned and points to valid memory (promised by caller) - unsafe { *arg_out = Self::as_ctype(val) }; - } -} - -/// Support for values passed to Rust by pointer. These are represented as opaque structs in C, -/// and always handled as pointers. -pub(crate) trait PassByPointer: Sized { - /// Take a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - arg must be a value returned from Box::into_raw (via return_ptr or ptr_to_arg_out) - /// - arg becomes invalid and must not be used after this call - unsafe fn take_from_ptr_arg(arg: *mut Self) -> Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { *(Box::from_raw(arg)) } - } - - /// Borrow a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid instance of Self - /// - arg must be valid for the lifetime assigned by the caller - /// - arg must not be modified by anything else during that lifetime - unsafe fn from_ptr_arg_ref<'a>(arg: *const Self) -> &'a Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { &*arg } - } - - /// Mutably borrow a value from C as an argument. - /// - /// # Safety - /// - /// - arg must not be NULL - /// - *arg must be a valid instance of Self - /// - arg must be valid for the lifetime assigned by the caller - /// - arg must not be accessed by anything else during that lifetime - unsafe fn from_ptr_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self { - debug_assert!(!arg.is_null()); - // SAFETY: see docstring - unsafe { &mut *arg } - } - - /// Return a value to C, transferring ownership - /// - /// # Safety - /// - /// - the caller must ensure that the value is eventually freed - unsafe fn return_ptr(self) -> *mut Self { - Box::into_raw(Box::new(self)) - } - - /// Return a value to C, transferring ownership, via an "output parameter". - /// - /// # Safety - /// - /// - the caller must ensure that the value is eventually freed - /// - arg_out must not be NULL - /// - arg_out must point to valid, properly aligned memory for a pointer value - unsafe fn ptr_to_arg_out(self, arg_out: *mut *mut Self) { - debug_assert!(!arg_out.is_null()); - // SAFETY: see docstring - unsafe { *arg_out = self.return_ptr() }; - } -} - -/// Support for C lists of objects referenced by value. -/// -/// The underlying C type should have three fields, containing items, length, and capacity. The -/// required trait functions just fetch and set these fields. -/// -/// The PassByValue trait will be implemented automatically, converting between the C type and -/// `Vec`. -/// -/// The element type can be PassByValue or PassByPointer. If the latter, it should use either -/// `NonNull` or `Option>` to represent the element. The latter is an "optional -/// pointer list", where elements can be omitted. -/// -/// For most cases, it is only necessary to implement `tc_.._free` that calls one of the -/// drop_..._list functions. -/// -/// # Safety -/// -/// The C type must be documented as read-only. None of the fields may be modified, nor anything -/// accessible via the `items` array. The exception is modification via "taking" elements. -/// -/// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0). -pub(crate) trait CList: Sized { - type Element; - - /// Create a new CList from the given items, len, and capacity. - /// - /// # Safety - /// - /// The arguments must either: - /// - be NULL, 0, and 0, respectively; or - /// - be valid for Vec::from_raw_parts - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self; - - /// Return a mutable slice representing the elements in this list. - fn slice(&mut self) -> &mut [Self::Element]; - - /// Get the items, len, and capacity (in that order) for this instance. These must be - /// precisely the same values passed tearlier to `from_raw_parts`. - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize); - - /// Generate a NULL value. By default this is a NULL items pointer with zero length and - /// capacity. - fn null_value() -> Self { - // SAFETY: - // - satisfies the first case in from_raw_parts' safety documentation - unsafe { Self::from_raw_parts(std::ptr::null_mut(), 0, 0) } - } -} - -/// Given a CList containing pass-by-value values, drop all of the values and -/// the list. -/// -/// This is a convenience function for `tc_.._list_free` functions. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_value_list(list: *mut CL) -where - CL: CList, - T: PassByValue, -{ - debug_assert!(!list.is_null()); - - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..) { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByValue::val_from_arg(e) }); - } - // then drop the vector - drop(vec); -} - -/// Given a CList containing NonNull pointers, drop all of the pointed-to values and the list. -/// -/// This is a convenience function for `tc_.._list_free` functions. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -#[allow(dead_code)] // this was useful once, and might be again? -pub(crate) unsafe fn drop_pointer_list(list: *mut CL) -where - CL: CList>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..) { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); - } - // then drop the vector - drop(vec); -} - -/// Given a CList containing optional pointers, drop all of the non-null pointed-to values and the -/// list. -/// -/// This is a convenience function for `tc_.._list_free` functions, for lists from which items -/// can be taken. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -/// - The caller must not use the value array points to after this function, as -/// it has been freed. It will be replaced with the null value. -pub(crate) unsafe fn drop_optional_pointer_list(list: *mut CL) -where - CL: CList>>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - // SAFETY: - // - *list is a valid CL (promised by caller) - let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) }; - - // first, drop each of the elements in turn - for e in vec.drain(..).flatten() { - // SAFETY: - // - e is a valid Element (promised by caller) - // - e is owned - drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) }); - } - // then drop the vector - drop(vec); -} - -/// Take a value from an optional pointer list, returning the value and replacing its array -/// element with NULL. -/// -/// This is a convenience function for `tc_.._list_take` functions, for lists from which items -/// can be taken. -/// -/// The returned value will be None if the element has already been taken, or if the index is -/// out of bounds. -/// -/// # Safety -/// -/// - List must be non-NULL and point to a valid CL instance -pub(crate) unsafe fn take_optional_pointer_list_item( - list: *mut CL, - index: usize, -) -> Option> -where - CL: CList>>, - T: PassByPointer, -{ - debug_assert!(!list.is_null()); - - // SAFETy: - // - list is properly aligned, dereferencable, and points to an initialized CL, since it is valid - // - the lifetime of the resulting reference is limited to this function, during which time - // nothing else refers to this memory. - let slice = unsafe { list.as_mut() }.unwrap().slice(); - if let Some(elt_ref) = slice.get_mut(index) { - let mut rv = None; - if let Some(elt) = elt_ref.as_mut() { - rv = Some(*elt); - *elt_ref = None; // clear out the array element - } - rv - } else { - None // index out of bounds - } -} - -impl PassByValue for A -where - A: CList, -{ - type RustType = Vec; - - unsafe fn from_ctype(self) -> Self::RustType { - let (items, len, cap) = self.into_raw_parts(); - debug_assert!(!items.is_null()); - // SAFETY: - // - CList::from_raw_parts requires that items, len, and cap be valid for - // Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller) - // - CList::into_raw_parts returns precisely the values passed to from_raw_parts. - // - those parts are passed to Vec::from_raw_parts here. - unsafe { Vec::from_raw_parts(items as *mut _, len, cap) } - } - - fn as_ctype(arg: Self::RustType) -> Self { - let (items, len, cap) = vec_into_raw_parts(arg); - // SAFETY: - // - satisfies the second case in from_raw_parts' safety documentation - unsafe { Self::from_raw_parts(items, len, cap) } - } -} diff --git a/taskchampion/lib/src/uda.rs b/taskchampion/lib/src/uda.rs deleted file mode 100644 index 3994bfc82..000000000 --- a/taskchampion/lib/src/uda.rs +++ /dev/null @@ -1,188 +0,0 @@ -use crate::traits::*; -use crate::types::*; - -#[ffizz_header::item] -#[ffizz(order = 500)] -/// ***** TCUda ***** -/// -/// TCUda contains the details of a UDA. -/// -/// ```c -/// typedef struct TCUda { -/// // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. -/// struct TCString ns; -/// // UDA key. Must not be NULL. -/// struct TCString key; -/// // Content of the UDA. Must not be NULL. -/// struct TCString value; -/// } TCUda; -/// ``` -#[repr(C)] -#[derive(Default)] -pub struct TCUda { - pub ns: TCString, - pub key: TCString, - pub value: TCString, -} - -pub(crate) struct Uda { - pub ns: Option>, - pub key: RustString<'static>, - pub value: RustString<'static>, -} - -impl PassByValue for TCUda { - type RustType = Uda; - - unsafe fn from_ctype(self) -> Self::RustType { - Uda { - ns: if self.ns.is_null() { - None - } else { - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.ns is a valid, non-null TCString (NULL just checked) - Some(unsafe { TCString::val_from_arg(self.ns) }) - }, - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.key is a valid, non-null TCString (see type docstring) - key: unsafe { TCString::val_from_arg(self.key) }, - // SAFETY: - // - self is owned, so we can take ownership of this TCString - // - self.value is a valid, non-null TCString (see type docstring) - value: unsafe { TCString::val_from_arg(self.value) }, - } - } - - fn as_ctype(uda: Uda) -> Self { - TCUda { - // SAFETY: caller assumes ownership of this value - ns: if let Some(ns) = uda.ns { - unsafe { TCString::return_val(ns) } - } else { - TCString::default() - }, - // SAFETY: caller assumes ownership of this value - key: unsafe { TCString::return_val(uda.key) }, - // SAFETY: caller assumes ownership of this value - value: unsafe { TCString::return_val(uda.value) }, - } - } -} - -#[ffizz_header::item] -#[ffizz(order = 510)] -/// ***** TCUdaList ***** -/// -/// TCUdaList represents a list of UDAs. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCUdaList { -/// // number of UDAs in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by -/// // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. -/// struct TCUda *items; -/// } TCUdaList; -/// ``` -#[repr(C)] -pub struct TCUdaList { - /// number of UDAs in items - len: libc::size_t, - - /// total size of items (internal use only) - _capacity: libc::size_t, - - /// Array of UDAs. These remain owned by the TCUdaList instance and will be freed by - /// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. - items: *mut TCUda, -} - -impl CList for TCUdaList { - type Element = TCUda; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCUdaList { - len, - _capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self._capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 501)] -/// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used -/// after this call. -/// -/// ```c -/// EXTERN_C void tc_uda_free(struct TCUda *tcuda); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) { - debug_assert!(!tcuda.is_null()); - // SAFETY: - // - *tcuda is a valid TCUda (caller promises to treat it as read-only) - let uda = unsafe { TCUda::take_val_from_arg(tcuda, TCUda::default()) }; - drop(uda); -} - -#[ffizz_header::item] -#[ffizz(order = 511)] -/// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. -/// -/// ```c -/// EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUdaList) { - // SAFETY: - // - tcudas is not NULL and points to a valid TCUdaList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcudas) } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; - assert!(!tcudas.items.is_null()); - assert_eq!(tcudas.len, 0); - assert_eq!(tcudas._capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcudas = unsafe { TCUdaList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_uda_list_free(&mut tcudas) }; - assert!(tcudas.items.is_null()); - assert_eq!(tcudas.len, 0); - assert_eq!(tcudas._capacity, 0); - } -} diff --git a/taskchampion/lib/src/util.rs b/taskchampion/lib/src/util.rs deleted file mode 100644 index bfd739282..000000000 --- a/taskchampion/lib/src/util.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::string::RustString; - -pub(crate) fn err_to_ruststring(e: impl std::string::ToString) -> RustString<'static> { - RustString::from(e.to_string()) -} - -/// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap. -pub(crate) fn vec_into_raw_parts(vec: Vec) -> (*mut T, usize, usize) { - // emulate Vec::into_raw_parts(): - // - disable dropping the Vec with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let mut vec = std::mem::ManuallyDrop::new(vec); - (vec.as_mut_ptr(), vec.len(), vec.capacity()) -} - -/// An implementation of String::into_raw_parts, which is still unstable. Returns ptr, len, cap. -pub(crate) fn string_into_raw_parts(string: String) -> (*mut u8, usize, usize) { - // emulate String::into_raw_parts(): - // - disable dropping the String with ManuallyDrop - // - extract ptr, len, and capacity using those methods - let mut string = std::mem::ManuallyDrop::new(string); - (string.as_mut_ptr(), string.len(), string.capacity()) -} diff --git a/taskchampion/lib/src/uuid.rs b/taskchampion/lib/src/uuid.rs deleted file mode 100644 index c979074ce..000000000 --- a/taskchampion/lib/src/uuid.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use libc; -use taskchampion::Uuid; - -#[ffizz_header::item] -#[ffizz(order = 300)] -/// ***** TCUuid ***** -/// -/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -/// Uuids are typically treated as opaque, but the bytes are available in big-endian format. -/// -/// ```c -/// typedef struct TCUuid { -/// uint8_t bytes[16]; -/// } TCUuid; -/// ``` -#[repr(C)] -#[derive(Debug, Default)] -pub struct TCUuid([u8; 16]); - -impl PassByValue for TCUuid { - type RustType = Uuid; - - unsafe fn from_ctype(self) -> Self::RustType { - // SAFETY: - // - any 16-byte value is a valid Uuid - Uuid::from_bytes(self.0) - } - - fn as_ctype(arg: Uuid) -> Self { - TCUuid(*arg.as_bytes()) - } -} - -#[ffizz_header::item] -#[ffizz(order = 301)] -/// Length, in bytes, of the string representation of a UUID (without NUL terminator) -/// -/// ```c -/// #define TC_UUID_STRING_BYTES 36 -/// ``` -// TODO: debug_assert or static_assert this somewhere? -pub const TC_UUID_STRING_BYTES: usize = 36; - -#[ffizz_header::item] -#[ffizz(order = 310)] -/// TCUuidList represents a list of uuids. -/// -/// The content of this struct must be treated as read-only. -/// -/// ```c -/// typedef struct TCUuidList { -/// // number of uuids in items -/// size_t len; -/// // reserved -/// size_t _u1; -/// // Array of uuids. This pointer is never NULL for a valid TCUuidList. -/// struct TCUuid *items; -/// } TCUuidList; -/// ``` -#[repr(C)] -pub struct TCUuidList { - /// number of uuids in items - len: libc::size_t, - - /// total size of items (internal use only) - capacity: libc::size_t, - - /// Array of uuids. This pointer is never NULL for a valid TCUuidList. - items: *mut TCUuid, -} - -impl CList for TCUuidList { - type Element = TCUuid; - - unsafe fn from_raw_parts(items: *mut Self::Element, len: usize, cap: usize) -> Self { - TCUuidList { - len, - capacity: cap, - items, - } - } - - fn slice(&mut self) -> &mut [Self::Element] { - // SAFETY: - // - because we have &mut self, we have read/write access to items[0..len] - // - all items are properly initialized Element's - // - return value lifetime is equal to &mmut self's, so access is exclusive - // - items and len came from Vec, so total size is < isize::MAX - unsafe { std::slice::from_raw_parts_mut(self.items, self.len) } - } - - fn into_raw_parts(self) -> (*mut Self::Element, usize, usize) { - (self.items, self.len, self.capacity) - } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Create a new, randomly-generated UUID. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_uuid_new_v4(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::new_v4()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Create a new UUID with the nil value. -/// -/// ```c -/// EXTERN_C struct TCUuid tc_uuid_nil(void); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid { - // SAFETY: - // - value is not allocated - unsafe { TCUuid::return_val(Uuid::nil()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Write the string representation of a TCUuid into the given buffer, which must be -/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. -/// -/// ```c -/// EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) { - debug_assert!(!buf.is_null()); - // SAFETY: - // - buf is valid for len bytes (by C convention) - // - (no alignment requirements for a byte slice) - // - content of buf will not be mutated during the lifetime of this slice (lifetime - // does not outlive this function call) - // - the length of the buffer is less than isize::MAX (promised by caller) - let buf: &mut [u8] = - unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, TC_UUID_STRING_BYTES) }; - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - uuid.as_hyphenated().encode_lower(buf); -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Return the hyphenated string representation of a TCUuid. The returned string -/// must be freed with tc_string_free. -/// -/// ```c -/// EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) }; - let s = uuid.to_string(); - // SAFETY: - // - caller promises to free this value. - unsafe { TCString::return_val(s.into()) } -} - -#[ffizz_header::item] -#[ffizz(order = 302)] -/// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given -/// string is not valid. -/// -/// ```c -/// EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult { - debug_assert!(!s.is_null()); - debug_assert!(!uuid_out.is_null()); - // SAFETY: - // - s is valid (promised by caller) - // - caller will not use s after this call (convention) - let mut s = unsafe { TCString::val_from_arg(s) }; - if let Ok(s) = s.as_str() { - if let Ok(u) = Uuid::parse_str(s) { - // SAFETY: - // - uuid_out is not NULL (promised by caller) - // - alignment is not required - unsafe { TCUuid::val_to_arg_out(u, uuid_out) }; - return TCResult::Ok; - } - } - TCResult::Error -} - -#[ffizz_header::item] -#[ffizz(order = 312)] -/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -/// this call. -/// -/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -/// -/// ```c -/// EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) { - // SAFETY: - // - tcuuids is not NULL and points to a valid TCUuidList (caller is not allowed to - // modify the list) - // - caller promises not to use the value after return - unsafe { drop_value_list(tcuuids) }; -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn empty_list_has_non_null_pointer() { - let tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; - assert!(!tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids.capacity, 0); - } - - #[test] - fn free_sets_null_pointer() { - let mut tcuuids = unsafe { TCUuidList::return_val(Vec::new()) }; - // SAFETY: testing expected behavior - unsafe { tc_uuid_list_free(&mut tcuuids) }; - assert!(tcuuids.items.is_null()); - assert_eq!(tcuuids.len, 0); - assert_eq!(tcuuids.capacity, 0); - } -} diff --git a/taskchampion/lib/src/workingset.rs b/taskchampion/lib/src/workingset.rs deleted file mode 100644 index ef9ceaf99..000000000 --- a/taskchampion/lib/src/workingset.rs +++ /dev/null @@ -1,141 +0,0 @@ -use crate::traits::*; -use crate::types::*; -use taskchampion::{Uuid, WorkingSet}; - -#[ffizz_header::item] -#[ffizz(order = 1100)] -/// ***** TCWorkingSet ***** -/// -/// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically -/// updated based on changes in the replica. Its lifetime is independent of the replica and it can -/// be freed at any time. -/// -/// To iterate over a working set, search indexes 1 through largest_index. -/// -/// # Safety -/// -/// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and -/// must later be freed to avoid a memory leak. Its lifetime is independent of the replica -/// from which it was generated. -/// -/// Any function taking a `*TCWorkingSet` requires: -/// - the pointer must not be NUL; -/// - the pointer must be one previously returned from `tc_replica_working_set` -/// - the memory referenced by the pointer must never be accessed by C code; and -/// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. -/// -/// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. -/// -/// TCWorkingSet is not threadsafe. -/// -/// ```c -/// typedef struct TCWorkingSet TCWorkingSet; -/// ``` -pub struct TCWorkingSet(WorkingSet); - -impl PassByPointer for TCWorkingSet {} - -impl From for TCWorkingSet { - fn from(ws: WorkingSet) -> TCWorkingSet { - TCWorkingSet(ws) - } -} - -/// Utility function to get a shared reference to the underlying WorkingSet. -fn wrap(ws: *mut TCWorkingSet, f: F) -> T -where - F: FnOnce(&WorkingSet) -> T, -{ - // SAFETY: - // - ws is not null (promised by caller) - // - ws outlives 'a (promised by caller) - let tcws: &TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) }; - f(&tcws.0) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set's length, or the number of UUIDs it contains. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize { - wrap(ws, |ws| ws.len()) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set's largest index. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize { - wrap(ws, |ws| ws.largest_index()) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the UUID for the task at the given index. Returns true if the UUID exists in the working -/// set. If not, returns false and does not change uuid_out. -/// -/// ```c -/// EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_by_index( - ws: *mut TCWorkingSet, - index: usize, - uuid_out: *mut TCUuid, -) -> bool { - debug_assert!(!uuid_out.is_null()); - wrap(ws, |ws| { - if let Some(uuid) = ws.by_index(index) { - // SAFETY: - // - uuid_out is not NULL (promised by caller) - // - alignment is not required - unsafe { TCUuid::val_to_arg_out(uuid, uuid_out) }; - true - } else { - false - } - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1101)] -/// Get the working set index for the task with the given UUID. Returns 0 if the task is not in -/// the working set. -/// -/// ```c -/// EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize { - wrap(ws, |ws| { - // SAFETY: - // - tcuuid is a valid TCUuid (all byte patterns are valid) - let uuid: Uuid = unsafe { TCUuid::val_from_arg(uuid) }; - ws.by_uuid(uuid).unwrap_or(0) - }) -} - -#[ffizz_header::item] -#[ffizz(order = 1102)] -/// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this -/// function returns, and must not be freed more than once. -/// -/// ```c -/// EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); -/// ``` -#[no_mangle] -pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) { - // SAFETY: - // - rep is not NULL (promised by caller) - // - caller will not use the TCWorkingSet after this (promised by caller) - let ws = unsafe { TCWorkingSet::take_from_ptr_arg(ws) }; - drop(ws); -} diff --git a/taskchampion/lib/taskchampion.h b/taskchampion/lib/taskchampion.h deleted file mode 100644 index 2f3237ab2..000000000 --- a/taskchampion/lib/taskchampion.h +++ /dev/null @@ -1,917 +0,0 @@ -// TaskChampion -// -// This file defines the C interface to libtaskchampion. This is a thin wrapper around the Rust -// `taskchampion` crate. Refer to the documentation for that crate at -// https://docs.rs/taskchampion/latest/taskchampion/ for API details. The comments in this file -// focus mostly on the low-level details of passing values to and from TaskChampion. -// -// # Overview -// -// This library defines four major types used to interact with the API, that map directly to Rust -// types. -// -// * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html -// * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html -// * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html -// * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html -// -// It also defines a few utility types: -// -// * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings. -// * TC…List - a list of objects represented as a C array -// * see below for the remainder -// -// # Safety -// -// Each type contains specific instructions to ensure memory safety. The general rules are as -// follows. -// -// No types in this library are threadsafe. All values should be used in only one thread for their -// entire lifetime. It is safe to use unrelated values in different threads (for example, -// different threads may use different TCReplica values concurrently). -// -// ## Pass by Pointer -// -// Several types such as TCReplica and TCString are "opaque" types and always handled as pointers -// in C. The bytes these pointers address are private to the Rust implementation and must not be -// accessed from C. -// -// Pass-by-pointer values have exactly one owner, and that owner is responsible for freeing the -// value (using a `tc_…_free` function), or transferring ownership elsewhere. Except where -// documented otherwise, when a value is passed to C, ownership passes to C as well. When a value -// is passed to Rust, ownership stays with the C code. The exception is TCString, ownership of -// which passes to Rust when it is used as a function argument. -// -// The limited circumstances where one value must not outlive another, due to pointer references -// between them, are documented below. -// -// ## Pass by Value -// -// Types such as TCUuid and TC…List are passed by value, and contain fields that are accessible -// from C. C code is free to access the content of these types in a _read_only_ fashion. -// -// Pass-by-value values that contain pointers also have exactly one owner, responsible for freeing -// the value or transferring ownership. The tc_…_free functions for these types will replace the -// pointers with NULL to guard against use-after-free errors. The interior pointers in such values -// should never be freed directly (for example, `tc_string_free(tcuda.value)` is an error). -// -// TCUuid is a special case, because it does not contain pointers. It can be freely copied and -// need not be freed. -// -// ## Lists -// -// Lists are a special kind of pass-by-value type. Each contains `len` and `items`, where `items` -// is an array of length `len`. Lists, and the values in the `items` array, must be treated as -// read-only. On return from an API function, a list's ownership is with the C caller, which must -// eventually free the list. List data must be freed with the `tc_…_list_free` function. It is an -// error to free any value in the `items` array of a list. - -#ifndef TASKCHAMPION_H -#define TASKCHAMPION_H - -#include -#include -#include - -#ifdef __cplusplus -#define EXTERN_C extern "C" -#else -#define EXTERN_C -#endif // __cplusplus - -// ***** TCResult ***** -// -// A result from a TC operation. Typically if this value is TC_RESULT_ERROR, -// the associated object's `tc_.._error` method will return an error message. -enum TCResult -#ifdef __cplusplus - : int32_t -#endif // __cplusplus -{ - TC_RESULT_ERROR = -1, - TC_RESULT_OK = 0, -}; -#ifndef __cplusplus -typedef int32_t TCResult; -#endif // __cplusplus - -// ***** TCString ***** -// -// TCString supports passing strings into and out of the TaskChampion API. -// -// # Rust Strings and C Strings -// -// A Rust string can contain embedded NUL characters, while C considers such a character to mark -// the end of a string. Strings containing embedded NULs cannot be represented as a "C string" -// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In -// general, these two functions should be used for handling arbitrary data, while more convenient -// forms may be used where embedded NUL characters are impossible, such as in static strings. -// -// # UTF-8 -// -// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given -// a `*TCString` containing invalid UTF-8. -// -// # Safety -// -// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All -// other fields in a TCString are private and must not be used from C. They exist in the struct -// to ensure proper allocation and alignment. -// -// When a `TCString` appears as a return value or output argument, ownership is passed to the -// caller. The caller must pass that ownership back to another function or free the string. -// -// Any function taking a `TCString` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; and -// - the memory referenced by the pointer must never be modified by C code. -// -// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is -// given as a function argument, and the caller must not use or free TCStrings after passing them -// to such API functions. -// -// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail -// for such a value. -// -// TCString is not threadsafe. -typedef struct TCString { - void *ptr; // opaque, but may be checked for NULL - size_t _u1; // reserved - size_t _u2; // reserved - uint8_t _u3; // reserved -} TCString; - -// Create a new TCString referencing the given C string. The C string must remain valid and -// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a -// static string. -// -// NOTE: this function does _not_ take responsibility for freeing the given C string. The -// given string can be freed once the TCString referencing it has been freed. -// -// For example: -// -// ```text -// char *url = get_item_url(..); // dynamically allocate C string -// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed -// free(url); // string is no longer referenced and can be freed -// ``` -EXTERN_C struct TCString tc_string_borrow(const char *cstr); - -// Create a new TCString by cloning the content of the given C string. The resulting TCString -// is independent of the given string, which can be freed or overwritten immediately. -EXTERN_C struct TCString tc_string_clone(const char *cstr); - -// Create a new TCString containing the given string with the given length. This allows creation -// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting -// TCString is independent of the passed buffer, which may be reused or freed immediately. -// -// The length should _not_ include any trailing NUL. -// -// The given length must be less than half the maximum value of usize. -EXTERN_C struct TCString tc_string_clone_with_len(const char *buf, size_t len); - -// Get the content of the string as a regular C string. The given string must be valid. The -// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The -// returned C string is valid until the TCString is freed or passed to another TC API function. -// -// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is -// valid and NUL-free. -// -// This function takes the TCString by pointer because it may be modified in-place to add a NUL -// terminator. The pointer must not be NULL. -// -// This function does _not_ take ownership of the TCString. -EXTERN_C const char *tc_string_content(const struct TCString *tcstring); - -// Get the content of the string as a pointer and length. The given string must not be NULL. -// This function can return any string, even one including NUL bytes or invalid UTF-8. The -// returned buffer is valid until the TCString is freed or passed to another TaskChampio -// function. -// -// This function takes the TCString by pointer because it may be modified in-place to add a NUL -// terminator. The pointer must not be NULL. -// -// This function does _not_ take ownership of the TCString. -EXTERN_C const char *tc_string_content_with_len(const struct TCString *tcstring, size_t *len_out); - -// Free a TCString. The given string must not be NULL. The string must not be used -// after this function returns, and must not be freed more than once. -EXTERN_C void tc_string_free(struct TCString *tcstring); - -// ***** TCStringList ***** -// -// TCStringList represents a list of strings. -// -// The content of this struct must be treated as read-only. -typedef struct TCStringList { - // number of strings in items - size_t len; - // reserved - size_t _u1; - // TCStringList representing each string. These remain owned by the TCStringList instance and will - // be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the - // *TCStringList at indexes 0..len-1 are not NULL. - struct TCString *items; -} TCStringList; - -// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList. -EXTERN_C void tc_string_list_free(struct TCStringList *tcstrings); - -// ***** TCUuid ***** -// -// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed. -// Uuids are typically treated as opaque, but the bytes are available in big-endian format. -typedef struct TCUuid { - uint8_t bytes[16]; -} TCUuid; - -// Length, in bytes, of the string representation of a UUID (without NUL terminator) -#define TC_UUID_STRING_BYTES 36 - -// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given -// string is not valid. -EXTERN_C TCResult tc_uuid_from_str(struct TCString s, struct TCUuid *uuid_out); - -// Create a new, randomly-generated UUID. -EXTERN_C struct TCUuid tc_uuid_new_v4(void); - -// Create a new UUID with the nil value. -EXTERN_C struct TCUuid tc_uuid_nil(void); - -// Write the string representation of a TCUuid into the given buffer, which must be -// at least TC_UUID_STRING_BYTES long. No NUL terminator is added. -EXTERN_C void tc_uuid_to_buf(struct TCUuid tcuuid, char *buf); - -// Return the hyphenated string representation of a TCUuid. The returned string -// must be freed with tc_string_free. -EXTERN_C struct TCString tc_uuid_to_str(struct TCUuid tcuuid); - -// TCUuidList represents a list of uuids. -// -// The content of this struct must be treated as read-only. -typedef struct TCUuidList { - // number of uuids in items - size_t len; - // reserved - size_t _u1; - // Array of uuids. This pointer is never NULL for a valid TCUuidList. - struct TCUuid *items; -} TCUuidList; - -// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList. -EXTERN_C void tc_uuid_list_free(struct TCUuidList *tcuuids); - -// ***** TCAnnotation ***** -// -// TCAnnotation contains the details of an annotation. -// -// # Safety -// -// An annotation must be initialized from a tc_.. function, and later freed -// with `tc_annotation_free` or `tc_annotation_list_free`. -// -// Any function taking a `*TCAnnotation` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - ownership transfers to the called function, and the value must not be used -// after the call returns. In fact, the value will be zeroed out to ensure this. -// -// TCAnnotations are not threadsafe. -typedef struct TCAnnotation { - // Time the annotation was made. Must be nonzero. - time_t entry; - // Content of the annotation. Must not be NULL. - TCString description; -} TCAnnotation; - -// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used -// after this call. -EXTERN_C void tc_annotation_free(struct TCAnnotation *tcann); - -// ***** TCAnnotationList ***** -// -// TCAnnotationList represents a list of annotations. -// -// The content of this struct must be treated as read-only. -typedef struct TCAnnotationList { - // number of annotations in items - size_t len; - // reserved - size_t _u1; - // Array of annotations. These remain owned by the TCAnnotationList instance and will be freed by - // tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList. - struct TCAnnotation *items; -} TCAnnotationList; - -// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList. -EXTERN_C void tc_annotation_list_free(struct TCAnnotationList *tcanns); - -// ***** TCUda ***** -// -// TCUda contains the details of a UDA. -typedef struct TCUda { - // Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field. - struct TCString ns; - // UDA key. Must not be NULL. - struct TCString key; - // Content of the UDA. Must not be NULL. - struct TCString value; -} TCUda; - -// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used -// after this call. -EXTERN_C void tc_uda_free(struct TCUda *tcuda); - -// ***** TCUdaList ***** -// -// TCUdaList represents a list of UDAs. -// -// The content of this struct must be treated as read-only. -typedef struct TCUdaList { - // number of UDAs in items - size_t len; - // reserved - size_t _u1; - // Array of UDAs. These remain owned by the TCUdaList instance and will be freed by - // tc_uda_list_free. This pointer is never NULL for a valid TCUdaList. - struct TCUda *items; -} TCUdaList; - -// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList. -EXTERN_C void tc_uda_list_free(struct TCUdaList *tcudas); - -// ***** TCKV ***** -// -// TCKV contains a key/value pair that is part of a task. -// -// Neither key nor value are ever NULL. They remain owned by the TCKV and -// will be freed when it is freed with tc_kv_list_free. -typedef struct TCKV { - struct TCString key; - struct TCString value; -} TCKV; - -// ***** TCKVList ***** -// -// TCKVList represents a list of key/value pairs. -// -// The content of this struct must be treated as read-only. -typedef struct TCKVList { - // number of key/value pairs in items - size_t len; - // reserved - size_t _u1; - // Array of TCKV's. These remain owned by the TCKVList instance and will be freed by - // tc_kv_list_free. This pointer is never NULL for a valid TCKVList. - struct TCKV *items; -} TCKVList; - -// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList. -EXTERN_C void tc_kv_list_free(struct TCKVList *tckvs); - -// ***** TCStatus ***** -// -// The status of a task, as defined by the task data model. -#ifdef __cplusplus -typedef enum TCStatus : int32_t { -#else // __cplusplus -typedef int32_t TCStatus; -enum TCStatus { -#endif // __cplusplus - TC_STATUS_PENDING = 0, - TC_STATUS_COMPLETED = 1, - TC_STATUS_DELETED = 2, - TC_STATUS_RECURRING = 3, - // Unknown signifies a status in the task DB that was not - // recognized. - TC_STATUS_UNKNOWN = -1, -#ifdef __cplusplus -} TCStatus; -#else // __cplusplus -}; -#endif // __cplusplus - -// ***** TCServer ***** -// -// TCServer represents an interface to a sync server. Aside from new and free, a server -// has no C-accessible API, but is designed to be passed to `tc_replica_sync`. -// -// ## Safety -// -// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously. -typedef struct TCServer TCServer; - -// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the -// description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_local(struct TCString server_dir, struct TCString *error_out); - -// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the -// description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_sync(struct TCString origin, - struct TCUuid client_id, - struct TCString encryption_secret, - struct TCString *error_out); - -// Create a new TCServer that connects to the Google Cloud Platform. See the TaskChampion docs -// for the description of the arguments. -// -// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is -// returned. The caller must free this string. -// -// The server must be freed after it is used - tc_replica_sync does not automatically free it. -EXTERN_C struct TCServer *tc_server_new_gcp(struct TCString bucket, - struct TCString credential_path, - struct TCString encryption_secret, - struct TCString *error_out); - -// Free a server. The server may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_server_free(struct TCServer *server); - -// ***** TCReplica ***** -// -// A replica represents an instance of a user's task data, providing an easy interface -// for querying and modifying that data. -// -// # Error Handling -// -// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then -// `tc_replica_error` will return the error message. -// -// # Safety -// -// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and -// must later be freed to avoid a memory leak. -// -// Any function taking a `*TCReplica` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller. -// -// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again. -// -// TCReplicas are not threadsafe. -typedef struct TCReplica TCReplica; - -// ***** TCReplicaOpType ***** -enum TCReplicaOpType -#ifdef __cplusplus - : uint32_t -#endif // __cplusplus -{ - Create = 0, - Delete = 1, - Update = 2, - UndoPoint = 3, -}; -#ifndef __cplusplus -typedef uint32_t TCReplicaOpType; -#endif // __cplusplus - -// ***** TCReplicaOp ***** -struct TCReplicaOp { - TCReplicaOpType operation_type; - void* inner; -}; - -typedef struct TCReplicaOp TCReplicaOp; - -// ***** TCReplicaOpList ***** -struct TCReplicaOpList { - struct TCReplicaOp *items; - size_t len; - size_t capacity; -}; - -typedef struct TCReplicaOpList TCReplicaOpList; - -// Create a new TCReplica with an in-memory database. The contents of the database will be -// lost when it is freed with tc_replica_free. -EXTERN_C struct TCReplica *tc_replica_new_in_memory(void); - -// Create a new TCReplica with an on-disk database having the given filename. On error, a string -// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller -// must free this string. -EXTERN_C struct TCReplica *tc_replica_new_on_disk(struct TCString path, - bool create_if_missing, - struct TCString *error_out); - -// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically -// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already -// been created by this Replica, and may be useful when a Replica instance is held for a long time -// and used to apply more than one user-visible change. -EXTERN_C TCResult tc_replica_add_undo_point(struct TCReplica *rep, bool force); - -// Get a list of all uuids for tasks in the replica. -// -// Returns a TCUuidList with a NULL items field on error. -// -// The caller must free the UUID list with `tc_uuid_list_free`. -EXTERN_C struct TCUuidList tc_replica_all_task_uuids(struct TCReplica *rep); - -// Get a list of all tasks in the replica. -// -// Returns a TCTaskList with a NULL items field on error. -EXTERN_C struct TCTaskList tc_replica_all_tasks(struct TCReplica *rep); - -// Undo local operations in storage. -// -// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if -// there are no operations that can be done. -EXTERN_C TCResult tc_replica_commit_undo_ops(struct TCReplica *rep, TCReplicaOpList tc_undo_ops, int32_t *undone_out); - -// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent -// calls to this function will return NULL. The rep pointer must not be NULL. The caller must -// free the returned string. -EXTERN_C struct TCString tc_replica_error(struct TCReplica *rep); - -// Get an existing task by its UUID. -// -// Returns NULL when the task does not exist, and on error. Consult tc_replica_error -// to distinguish the two conditions. -EXTERN_C struct TCTask *tc_replica_get_task(struct TCReplica *rep, struct TCUuid tcuuid); - -// Return undo local operations until the most recent UndoPoint. -EXTERN_C TCReplicaOpList tc_replica_get_undo_ops(struct TCReplica *rep); - -// Create a new task. The task must not already exist. -// -// Returns the task, or NULL on error. -EXTERN_C struct TCTask *tc_replica_import_task_with_uuid(struct TCReplica *rep, struct TCUuid tcuuid); - -// Create a new task. The task must not already exist. -// -// Returns the task, or NULL on error. -EXTERN_C struct TCTask *tc_replica_new_task(struct TCReplica *rep, - enum TCStatus status, - struct TCString description); - -// Get the number of local, un-synchronized operations (not including undo points), or -1 on -// error. -EXTERN_C int64_t tc_replica_num_local_operations(struct TCReplica *rep); - -// Get the number of undo points (number of undo calls possible), or -1 on error. -EXTERN_C int64_t tc_replica_num_undo_points(struct TCReplica *rep); - -// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber` -// is true, then existing tasks may be moved to new working-set indices; in any case, on -// completion all pending tasks are in the working set and all non- pending tasks are not. -EXTERN_C TCResult tc_replica_rebuild_working_set(struct TCReplica *rep, bool renumber); - -// Synchronize this replica with a server. -// -// The `server` argument remains owned by the caller, and must be freed explicitly. -EXTERN_C TCResult tc_replica_sync(struct TCReplica *rep, struct TCServer *server, bool avoid_snapshots); - -// Get the current working set for this replica. The resulting value must be freed -// with tc_working_set_free. -// -// Returns NULL on error. -EXTERN_C struct TCWorkingSet *tc_replica_working_set(struct TCReplica *rep); - -// Free a replica. The replica may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_replica_free(struct TCReplica *rep); - -// Return description field of old task field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_old_task_description(struct TCReplicaOp *op); - -// Return old value field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_old_value(struct TCReplicaOp *op); - -// Return property field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_property(struct TCReplicaOp *op); - -// Return timestamp field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_timestamp(struct TCReplicaOp *op); - -// Return uuid field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_uuid(struct TCReplicaOp *op); - -// Return value field of ReplicaOp. -EXTERN_C struct TCString tc_replica_op_get_value(struct TCReplicaOp *op); - -// Free a vector of ReplicaOp. The vector may not be used after this function returns and must not be freed -// more than once. -EXTERN_C void tc_replica_op_list_free(struct TCReplicaOpList *oplist); - -// ***** TCTask ***** -// -// A task, as publicly exposed by this library. -// -// A task begins in "immutable" mode. It must be converted to "mutable" mode -// to make any changes, and doing so requires exclusive access to the replica -// until the task is freed or converted back to immutable mode. -// -// An immutable task carries no reference to the replica that created it, and can be used until it -// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and -// must be freed or made immutable before the replica is freed. -// -// All `tc_task_..` functions taking a task as an argument require that it not be NULL. -// -// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then -// `tc_task_error` will return the error message. -// -// # Safety -// -// A task is an owned object, and must be freed with tc_task_free (or, if part of a list, -// with tc_task_list_free). -// -// Any function taking a `*TCTask` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from a tc_… function; -// - the memory referenced by the pointer must never be modified by C code; and -// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller. -// -// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again. -// -// TCTasks are not threadsafe. -typedef struct TCTask TCTask; - -// Get the annotations for the task. -// -// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not -// reference the task and the two may be freed in any order. -EXTERN_C struct TCAnnotationList tc_task_get_annotations(struct TCTask *task); - -// Get all dependencies for a task. -EXTERN_C struct TCUuidList tc_task_get_dependencies(struct TCTask *task); - -// Get a task's description. -EXTERN_C struct TCString tc_task_get_description(struct TCTask *task); - -// Get the entry timestamp for a task (when it was created), or 0 if not set. -EXTERN_C time_t tc_task_get_entry(struct TCTask *task); - -// Get the named legacy UDA from the task. -// -// Returns NULL if the UDA does not exist. -EXTERN_C struct TCString tc_task_get_legacy_uda(struct TCTask *task, struct TCString key); - -// Get all UDAs for this task. -// -// All TCUdas in this list have a NULL ns field. The entire UDA key is -// included in the key field. The caller must free the returned list. -EXTERN_C struct TCUdaList tc_task_get_legacy_udas(struct TCTask *task); - -// Get the modified timestamp for a task, or 0 if not set. -EXTERN_C time_t tc_task_get_modified(struct TCTask *task); - -// Get a task's status. -EXTERN_C enum TCStatus tc_task_get_status(struct TCTask *task); - -// Get the tags for the task. -// -// The caller must free the returned TCStringList instance. The TCStringList instance does not -// reference the task and the two may be freed in any order. -EXTERN_C struct TCStringList tc_task_get_tags(struct TCTask *task); - -// Get the underlying key/value pairs for this task. The returned TCKVList is -// a "snapshot" of the task and will not be updated if the task is subsequently -// modified. It is the caller's responsibility to free the TCKVList. -EXTERN_C struct TCKVList tc_task_get_taskmap(struct TCTask *task); - -// Get the named UDA from the task. -// -// Returns a TCString with NULL ptr field if the UDA does not exist. -EXTERN_C struct TCString tc_task_get_uda(struct TCTask *task, struct TCString ns, struct TCString key); - -// Get all UDAs for this task. -// -// Legacy UDAs are represented with an empty string in the ns field. -EXTERN_C struct TCUdaList tc_task_get_udas(struct TCTask *task); - -// Get a task's UUID. -EXTERN_C struct TCUuid tc_task_get_uuid(struct TCTask *task); - -// Get a task property's value, or NULL if the task has no such property, (including if the -// property name is not valid utf-8). -EXTERN_C struct TCString tc_task_get_value(struct TCTask *task, struct TCString property); - -// Get the wait timestamp for a task, or 0 if not set. -EXTERN_C time_t tc_task_get_wait(struct TCTask *task); - -// Check if a task has the given tag. If the tag is invalid, this function will return false, as -// that (invalid) tag is not present. No error will be reported via `tc_task_error`. -EXTERN_C bool tc_task_has_tag(struct TCTask *task, struct TCString tag); - -// Check if a task is active (started and not stopped). -EXTERN_C bool tc_task_is_active(struct TCTask *task); - -// Check if a task is blocked (depends on at least one other task). -EXTERN_C bool tc_task_is_blocked(struct TCTask *task); - -// Check if a task is blocking (at least one other task depends on it). -EXTERN_C bool tc_task_is_blocking(struct TCTask *task); - -// Check if a task is waiting. -EXTERN_C bool tc_task_is_waiting(struct TCTask *task); - -// Convert an immutable task into a mutable task. -// -// The task must not be NULL. It is modified in-place, and becomes mutable. -// -// The replica must not be NULL. After this function returns, the replica _cannot be used at all_ -// until this task is made immutable again. This implies that it is not allowed for more than one -// task associated with a replica to be mutable at any time. -// -// Typically mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`: -// -// ```text -// tc_task_to_mut(task, rep); -// success = tc_task_done(task); -// tc_task_to_immut(task, rep); -// if (!success) { ... } -// ``` -EXTERN_C void tc_task_to_mut(struct TCTask *task, struct TCReplica *tcreplica); - -// Add an annotation to a mutable task. This call takes ownership of the -// passed annotation, which must not be used after the call returns. -EXTERN_C TCResult tc_task_add_annotation(struct TCTask *task, struct TCAnnotation *annotation); - -// Add a dependency. -EXTERN_C TCResult tc_task_add_dependency(struct TCTask *task, struct TCUuid dep); - -// Add a tag to a mutable task. -EXTERN_C TCResult tc_task_add_tag(struct TCTask *task, struct TCString tag); - -// Mark a task as deleted. -EXTERN_C TCResult tc_task_delete(struct TCTask *task); - -// Mark a task as done. -EXTERN_C TCResult tc_task_done(struct TCTask *task); - -// Remove an annotation from a mutable task. -EXTERN_C TCResult tc_task_remove_annotation(struct TCTask *task, int64_t entry); - -// Remove a dependency. -EXTERN_C TCResult tc_task_remove_dependency(struct TCTask *task, struct TCUuid dep); - -// Remove a UDA fraom a mutable task. -EXTERN_C TCResult tc_task_remove_legacy_uda(struct TCTask *task, struct TCString key); - -// Remove a tag from a mutable task. -EXTERN_C TCResult tc_task_remove_tag(struct TCTask *task, struct TCString tag); - -// Remove a UDA fraom a mutable task. -EXTERN_C TCResult tc_task_remove_uda(struct TCTask *task, struct TCString ns, struct TCString key); - -// Set a mutable task's description. -EXTERN_C TCResult tc_task_set_description(struct TCTask *task, struct TCString description); - -// Set a mutable task's entry (creation time). Pass entry=0 to unset -// the entry field. -EXTERN_C TCResult tc_task_set_entry(struct TCTask *task, time_t entry); - -// Set a legacy UDA on a mutable task. -EXTERN_C TCResult tc_task_set_legacy_uda(struct TCTask *task, struct TCString key, struct TCString value); - -// Set a mutable task's modified timestamp. The value cannot be zero. -EXTERN_C TCResult tc_task_set_modified(struct TCTask *task, time_t modified); - -// Set a mutable task's status. -EXTERN_C TCResult tc_task_set_status(struct TCTask *task, enum TCStatus status); - -// Set a UDA on a mutable task. -EXTERN_C TCResult tc_task_set_uda(struct TCTask *task, - struct TCString ns, - struct TCString key, - struct TCString value); - -// Set a mutable task's property value by name. If value.ptr is NULL, the property is removed. -EXTERN_C TCResult tc_task_set_value(struct TCTask *task, struct TCString property, struct TCString value); - -// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field. -EXTERN_C TCResult tc_task_set_wait(struct TCTask *task, time_t wait); - -// Start a task. -EXTERN_C TCResult tc_task_start(struct TCTask *task); - -// Stop a task. -EXTERN_C TCResult tc_task_stop(struct TCTask *task); - -// Convert a mutable task into an immutable task. -// -// The task must not be NULL. It is modified in-place, and becomes immutable. -// -// The replica passed to `tc_task_to_mut` may be used freely after this call. -EXTERN_C void tc_task_to_immut(struct TCTask *task); - -// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded. -// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The -// caller must free the returned string. -EXTERN_C struct TCString tc_task_error(struct TCTask *task); - -// Free a task. The given task must not be NULL. The task must not be used after this function -// returns, and must not be freed more than once. -// -// If the task is currently mutable, it will first be made immutable. -EXTERN_C void tc_task_free(struct TCTask *task); - -// ***** TCTaskList ***** -// -// TCTaskList represents a list of tasks. -// -// The content of this struct must be treated as read-only: no fields or anything they reference -// should be modified directly by C code. -// -// When an item is taken from this list, its pointer in `items` is set to NULL. -typedef struct TCTaskList { - // number of tasks in items - size_t len; - // reserved - size_t _u1; - // Array of pointers representing each task. These remain owned by the TCTaskList instance and - // will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList. - // Pointers in the array may be NULL after `tc_task_list_take`. - struct TCTask **items; -} TCTaskList; - -// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after -// this call. -// -// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList. -EXTERN_C void tc_task_list_free(struct TCTaskList *tasks); - -// Take an item from a TCTaskList. After this call, the indexed item is no longer associated -// with the list and becomes the caller's responsibility, just as if it had been returned from -// `tc_replica_get_task`. -// -// The corresponding element in the `items` array will be set to NULL. If that field is already -// NULL (that is, if the item has already been taken), this function will return NULL. If the -// index is out of bounds, this function will also return NULL. -// -// The passed TCTaskList remains owned by the caller. -EXTERN_C struct TCTask *tc_task_list_take(struct TCTaskList *tasks, size_t index); - -// ***** TCWorkingSet ***** -// -// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically -// updated based on changes in the replica. Its lifetime is independent of the replica and it can -// be freed at any time. -// -// To iterate over a working set, search indexes 1 through largest_index. -// -// # Safety -// -// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and -// must later be freed to avoid a memory leak. Its lifetime is independent of the replica -// from which it was generated. -// -// Any function taking a `*TCWorkingSet` requires: -// - the pointer must not be NUL; -// - the pointer must be one previously returned from `tc_replica_working_set` -// - the memory referenced by the pointer must never be accessed by C code; and -// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller. -// -// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again. -// -// TCWorkingSet is not threadsafe. -typedef struct TCWorkingSet TCWorkingSet; - -// Get the UUID for the task at the given index. Returns true if the UUID exists in the working -// set. If not, returns false and does not change uuid_out. -EXTERN_C bool tc_working_set_by_index(struct TCWorkingSet *ws, size_t index, struct TCUuid *uuid_out); - -// Get the working set index for the task with the given UUID. Returns 0 if the task is not in -// the working set. -EXTERN_C size_t tc_working_set_by_uuid(struct TCWorkingSet *ws, struct TCUuid uuid); - -// Get the working set's largest index. -EXTERN_C size_t tc_working_set_largest_index(struct TCWorkingSet *ws); - -// Get the working set's length, or the number of UUIDs it contains. -EXTERN_C size_t tc_working_set_len(struct TCWorkingSet *ws); - -// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this -// function returns, and must not be freed more than once. -EXTERN_C void tc_working_set_free(struct TCWorkingSet *ws); - -#endif /* TASKCHAMPION_H */ diff --git a/taskchampion/scripts/changelog.py b/taskchampion/scripts/changelog.py deleted file mode 100755 index 0eac4fa35..000000000 --- a/taskchampion/scripts/changelog.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -import os -import argparse -import datetime -import subprocess -from typing import List - -def ymd(): - return datetime.datetime.now().strftime("%Y-%m-%d") - -def git_current_branch() -> str : - out = subprocess.check_output(["git", "branch", "--show-current"]) - return out.strip().decode("utf-8") - -def get_dir() -> str: - here = os.path.dirname(os.path.abspath(__file__)) - return os.path.join( - here, - "../.changelogs") - -def get_changefiles() -> List[str]: - changedir = get_dir() - changefiles = [] - for f in os.listdir(changedir): - if f.endswith(".md") and not f.startswith("."): - changefiles.append(os.path.join(changedir, f)) - - return changefiles - -def cmd_add(args): - text = args.text.strip() - if not text.startswith("- "): - text = "- %s" % text - - timestamp = ymd() - branchname = git_current_branch() - fname = os.path.join(get_dir(), "%s-%s.md" % (timestamp, branchname)) - with open(fname, "a") as f: - f.write(text) - f.write("\n") - -def cmd_build(args): - print("## x.y.z - %s" % (ymd())) - for e in get_changefiles(): - print(open(e).read().strip()) - -def main() -> None: - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(title='Sub commands', dest='command') - subparsers.required = True - - parser_add = subparsers.add_parser('add') - parser_add.add_argument("text") - parser_add.set_defaults(func=cmd_add) - - parser_build = subparsers.add_parser('build') - parser_build.set_defaults(func=cmd_build) - - args = parser.parse_args() - args.func(args) - - -if __name__ == "__main__": - main() diff --git a/taskchampion/taskchampion/Cargo.toml b/taskchampion/taskchampion/Cargo.toml deleted file mode 100644 index 7ff582dd7..000000000 --- a/taskchampion/taskchampion/Cargo.toml +++ /dev/null @@ -1,58 +0,0 @@ -[package] -name = "taskchampion" -version = "0.4.1" -authors = ["Dustin J. Mitchell "] -description = "Personal task-tracking" -homepage = "https://gothenburgbitfactory.github.io/taskwarrior/taskchampion/" -documentation = "https://docs.rs/crate/taskchampion" -repository = "https://github.com/GothenburgBitFactory/taskwarrior" -readme = "../README.md" -license = "MIT" -edition = "2021" -rust-version = "1.70.0" - -[features] -default = ["server-sync", "server-gcp"] - -# Support for sync to a server -server-sync = ["encryption", "dep:ureq", "dep:url"] -# Support for sync to GCP -server-gcp = ["cloud", "encryption", "dep:google-cloud-storage", "dep:tokio"] -# (private) Support for sync protocol encryption -encryption = ["dep:ring"] -# (private) Generic support for cloud sync -cloud = [] - -[package.metadata.docs.rs] -all-features = true - -[dependencies] -uuid.workspace = true -serde.workspace = true -serde_json.workspace = true -chrono.workspace = true -anyhow.workspace = true -thiserror.workspace = true -ureq.workspace = true -log.workspace = true -rusqlite.workspace = true -strum.workspace = true -strum_macros.workspace = true -flate2.workspace = true -byteorder.workspace = true -ring.workspace = true -google-cloud-storage.workspace = true -tokio.workspace = true -url.workspace = true - -google-cloud-storage.optional = true -tokio.optional = true -ureq.optional = true -url.optional = true -ring.optional = true - -[dev-dependencies] -proptest.workspace = true -tempfile.workspace = true -rstest.workspace = true -pretty_assertions.workspace = true diff --git a/taskchampion/taskchampion/src/depmap.rs b/taskchampion/taskchampion/src/depmap.rs deleted file mode 100644 index fe4d5df6f..000000000 --- a/taskchampion/taskchampion/src/depmap.rs +++ /dev/null @@ -1,81 +0,0 @@ -use uuid::Uuid; - -/// DependencyMap stores information on task dependencies between pending tasks. -/// -/// This information requires a scan of the working set to generate, so it is -/// typically calculated once and re-used. -#[derive(Debug, PartialEq, Eq)] -pub struct DependencyMap { - /// Edges of the dependency graph. If (a, b) is in this array, then task a depends on tsak b. - edges: Vec<(Uuid, Uuid)>, -} - -impl DependencyMap { - /// Create a new, empty DependencyMap. - pub(super) fn new() -> Self { - Self { edges: Vec::new() } - } - - /// Add a dependency of a on b. - pub(super) fn add_dependency(&mut self, a: Uuid, b: Uuid) { - self.edges.push((a, b)); - } - - /// Return an iterator of Uuids on which task `deps_of` depends. This is equivalent to - /// `task.get_dependencies()`. - pub fn dependencies(&self, dep_of: Uuid) -> impl Iterator + '_ { - self.edges - .iter() - .filter_map(move |(a, b)| if a == &dep_of { Some(*b) } else { None }) - } - - /// Return an iterator of Uuids of tasks that depend on `dep_on` - /// `task.get_dependencies()`. - pub fn dependents(&self, dep_on: Uuid) -> impl Iterator + '_ { - self.edges - .iter() - .filter_map(move |(a, b)| if b == &dep_on { Some(*a) } else { None }) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - use std::collections::HashSet; - - #[test] - fn dependencies() { - let t = Uuid::new_v4(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let mut dm = DependencyMap::new(); - - dm.add_dependency(t, uuid1); - dm.add_dependency(t, uuid2); - dm.add_dependency(Uuid::new_v4(), t); - dm.add_dependency(Uuid::new_v4(), uuid1); - dm.add_dependency(uuid2, Uuid::new_v4()); - - assert_eq!( - dm.dependencies(t).collect::>(), - set![uuid1, uuid2] - ); - } - - #[test] - fn dependents() { - let t = Uuid::new_v4(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let mut dm = DependencyMap::new(); - - dm.add_dependency(uuid1, t); - dm.add_dependency(uuid2, t); - dm.add_dependency(t, Uuid::new_v4()); - dm.add_dependency(Uuid::new_v4(), uuid1); - dm.add_dependency(uuid2, Uuid::new_v4()); - - assert_eq!(dm.dependents(t).collect::>(), set![uuid1, uuid2]); - } -} diff --git a/taskchampion/taskchampion/src/errors.rs b/taskchampion/taskchampion/src/errors.rs deleted file mode 100644 index 031c6ce93..000000000 --- a/taskchampion/taskchampion/src/errors.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::io; -use thiserror::Error; - -#[derive(Debug, Error)] -#[non_exhaustive] -/// Errors returned from taskchampion operations -pub enum Error { - /// A server-related error - #[error("Server Error: {0}")] - Server(String), - /// A task-database-related error - #[error("Task Database Error: {0}")] - Database(String), - /// An error specifically indicating that the local replica cannot - /// be synchronized with the sever, due to being out of date or some - /// other irrecoverable error. - #[error("Local replica is out of sync with the server")] - OutOfSync, - /// A usage error - #[error("Usage Error: {0}")] - Usage(String), - /// A general error. - #[error(transparent)] - Other(#[from] anyhow::Error), -} - -/// Convert private and third party errors into Error::Other. -macro_rules! other_error { - ( $error:ty ) => { - impl From<$error> for Error { - fn from(err: $error) -> Self { - Self::Other(err.into()) - } - } - }; -} -#[cfg(feature = "server-sync")] -other_error!(ureq::Error); -other_error!(io::Error); -other_error!(serde_json::Error); -other_error!(rusqlite::Error); -other_error!(crate::storage::sqlite::SqliteError); -#[cfg(feature = "server-gcp")] -other_error!(google_cloud_storage::http::Error); -#[cfg(feature = "server-gcp")] -other_error!(google_cloud_storage::client::google_cloud_auth::error::Error); - -pub type Result = std::result::Result; diff --git a/taskchampion/taskchampion/src/lib.rs b/taskchampion/taskchampion/src/lib.rs deleted file mode 100644 index c9408586f..000000000 --- a/taskchampion/taskchampion/src/lib.rs +++ /dev/null @@ -1,82 +0,0 @@ -#![deny(clippy::all)] -/*! - -This crate implements the core of TaskChampion, the [replica](crate::Replica). - -Users of this crate can manipulate a task database using this API, including synchronizing that task database with others via a synchronization server. - -Example uses of this crate: - * user interfaces for task management, such as mobile apps, web apps, or command-line interfaces - * integrations for task management, such as synchronization with ticket-tracking systems or - request forms. - -# Replica - -A TaskChampion replica is a local copy of a user's task data. As the name suggests, several -replicas of the same data can exist (such as on a user's laptop and on their phone) and can -synchronize with one another. - -Replicas are accessed using the [`Replica`](crate::Replica) type. - -# Task Storage - -Replicas access the task database via a [storage object](crate::storage::Storage). -Create a storage object with [`StorageConfig`](crate::storage::StorageConfig). - -The [`storage`](crate::storage) module supports pluggable storage for a replica's data. -An implementation is provided, but users of this crate can provide their own implementation as well. - -# Server - -Replica synchronization takes place against a server. -Create a server with [`ServerConfig`](crate::ServerConfig). - -The [`server`](crate::server) module defines the interface a server must meet. -Users can define their own server impelementations. - -# Feature Flags - -Support for some optional functionality is controlled by feature flags. - -Sync server client support: - - * `server-gcp` - sync to Google Cloud Platform - * `server-sync` - sync to the taskchampion-sync-server - -# See Also - -See the [TaskChampion Book](http://taskchampion.github.com/taskchampion) -for more information about the design and usage of the tool. - -# Minimum Supported Rust Version (MSRV) - -This crate supports Rust version 1.70.0 and higher. - - */ - -// NOTE: it's important that this 'mod' comes first so that the macros can be used in other modules -mod macros; - -mod depmap; -mod errors; -mod replica; -pub mod server; -pub mod storage; -mod task; -mod taskdb; -mod utils; -mod workingset; - -pub use depmap::DependencyMap; -pub use errors::Error; -pub use replica::Replica; -pub use server::{Server, ServerConfig}; -pub use storage::StorageConfig; -pub use task::{utc_timestamp, Annotation, Status, Tag, Task, TaskMut}; -pub use workingset::WorkingSet; - -/// Re-exported type from the `uuid` crate, for ease of compatibility for consumers of this crate. -pub use uuid::Uuid; - -/// Re-exported chrono module. -pub use chrono; diff --git a/taskchampion/taskchampion/src/macros.rs b/taskchampion/taskchampion/src/macros.rs deleted file mode 100644 index eb34b4640..000000000 --- a/taskchampion/taskchampion/src/macros.rs +++ /dev/null @@ -1,17 +0,0 @@ -#![macro_use] - -/// Create a hashset, similar to vec! -// NOTE: in Rust 1.56.0, this can be changed to HashSet::from([..]) -#[cfg(test)] -macro_rules! set( - { $($key:expr),* $(,)? } => { - { - #[allow(unused_mut)] - let mut s = ::std::collections::HashSet::new(); - $( - s.insert($key); - )* - s - } - }; -); diff --git a/taskchampion/taskchampion/src/replica.rs b/taskchampion/taskchampion/src/replica.rs deleted file mode 100644 index bad6cceba..000000000 --- a/taskchampion/taskchampion/src/replica.rs +++ /dev/null @@ -1,677 +0,0 @@ -use crate::depmap::DependencyMap; -use crate::errors::Result; -use crate::server::{Server, SyncOp}; -use crate::storage::{ReplicaOp, Storage, TaskMap}; -use crate::task::{Status, Task}; -use crate::taskdb::TaskDb; -use crate::workingset::WorkingSet; -use anyhow::Context; -use chrono::{Duration, Utc}; -use log::trace; -use std::collections::HashMap; -use std::rc::Rc; -use uuid::Uuid; - -/// A replica represents an instance of a user's task data, providing an easy interface -/// for querying and modifying that data. -/// -/// ## Tasks -/// -/// Tasks are uniquely identified by UUIDs. -/// Most task modifications are performed via the [`Task`](crate::Task) and -/// [`TaskMut`](crate::TaskMut) types. Use of two types for tasks allows easy -/// read-only manipulation of lots of tasks, with exclusive access required only -/// for modifications. -/// -/// ## Working Set -/// -/// A replica maintains a "working set" of tasks that are of current concern to the user, -/// specifically pending tasks. These are indexed with small, easy-to-type integers. Newly -/// pending tasks are automatically added to the working set, and the working set is "renumbered" -/// during the garbage-collection process. -pub struct Replica { - taskdb: TaskDb, - - /// If true, this replica has already added an undo point. - added_undo_point: bool, - - /// The dependency map for this replica, if it has been calculated. - depmap: Option>, -} - -impl Replica { - pub fn new(storage: Box) -> Replica { - Replica { - taskdb: TaskDb::new(storage), - added_undo_point: false, - depmap: None, - } - } - - #[cfg(test)] - pub fn new_inmemory() -> Replica { - Replica::new(Box::new(crate::storage::InMemoryStorage::new())) - } - - /// Update an existing task. If the value is Some, the property is added or updated. If the - /// value is None, the property is deleted. It is not an error to delete a nonexistent - /// property. - /// - /// This is a low-level method, and requires knowledge of the Task data model. Prefer to - /// use the [`TaskMut`] methods to modify tasks, where possible. - pub fn update_task( - &mut self, - uuid: Uuid, - property: S1, - value: Option, - ) -> Result - where - S1: Into, - S2: Into, - { - self.add_undo_point(false)?; - self.taskdb.apply(SyncOp::Update { - uuid, - property: property.into(), - value: value.map(|v| v.into()), - timestamp: Utc::now(), - }) - } - - /// Add the given uuid to the working set, returning its index. - pub(crate) fn add_to_working_set(&mut self, uuid: Uuid) -> Result { - self.taskdb.add_to_working_set(uuid) - } - - /// Get all tasks represented as a map keyed by UUID - pub fn all_tasks(&mut self) -> Result> { - let depmap = self.dependency_map(false)?; - let mut res = HashMap::new(); - for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) { - res.insert(uuid, Task::new(uuid, tm, depmap.clone())); - } - Ok(res) - } - - /// Get the UUIDs of all tasks - pub fn all_task_uuids(&mut self) -> Result> { - self.taskdb.all_task_uuids() - } - - /// Get the "working set" for this replica. This is a snapshot of the current state, - /// and it is up to the caller to decide how long to store this value. - pub fn working_set(&mut self) -> Result { - Ok(WorkingSet::new(self.taskdb.working_set()?)) - } - - /// Get the dependency map for all pending tasks. - /// - /// A task dependency is recognized when a task in the working set depends on a task with - /// status equal to Pending. - /// - /// The data in this map is cached when it is first requested and may not contain modifications - /// made locally in this Replica instance. The result is reference-counted and may - /// outlive the Replica. - /// - /// If `force` is true, then the result is re-calculated from the current state of the replica, - /// although previously-returned dependency maps are not updated. - pub fn dependency_map(&mut self, force: bool) -> Result> { - if force || self.depmap.is_none() { - // note: we can't use self.get_task here, as that depends on a - // DependencyMap - - let mut dm = DependencyMap::new(); - // temporary cache tracking whether tasks are considered Pending or not. - let mut is_pending_cache: HashMap = HashMap::new(); - let ws = self.working_set()?; - // for each task in the working set - for i in 1..=ws.largest_index() { - // get the task UUID - if let Some(u) = ws.by_index(i) { - // get the task - if let Some(taskmap) = self.taskdb.get_task(u)? { - // search the task's keys - for p in taskmap.keys() { - // for one matching `dep_..` - if let Some(dep_str) = p.strip_prefix("dep_") { - // and extract the UUID from the key - if let Ok(dep) = Uuid::parse_str(dep_str) { - // the dependency is pending if - let dep_pending = { - // we've determined this before and cached the result - if let Some(dep_pending) = is_pending_cache.get(&dep) { - *dep_pending - } else if let Some(dep_taskmap) = - // or if we get the task - self.taskdb.get_task(dep)? - { - // and its status is "pending" - let dep_pending = matches!( - dep_taskmap - .get("status") - .map(|tm| Status::from_taskmap(tm)), - Some(Status::Pending) - ); - is_pending_cache.insert(dep, dep_pending); - dep_pending - } else { - false - } - }; - if dep_pending { - dm.add_dependency(u, dep); - } - } - } - } - } - } - } - self.depmap = Some(Rc::new(dm)); - } - - // at this point self.depmap is guaranteed to be Some(_) - Ok(self.depmap.as_ref().unwrap().clone()) - } - - /// Get an existing task by its UUID - pub fn get_task(&mut self, uuid: Uuid) -> Result> { - let depmap = self.dependency_map(false)?; - Ok(self - .taskdb - .get_task(uuid)? - .map(move |tm| Task::new(uuid, tm, depmap))) - } - - /// Create a new task. - pub fn new_task(&mut self, status: Status, description: String) -> Result { - let uuid = Uuid::new_v4(); - self.add_undo_point(false)?; - let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; - let depmap = self.dependency_map(false)?; - let mut task = Task::new(uuid, taskmap, depmap).into_mut(self); - task.set_description(description)?; - task.set_status(status)?; - task.set_entry(Some(Utc::now()))?; - trace!("task {} created", uuid); - Ok(task.into_immut()) - } - - /// Create a new, empty task with the given UUID. This is useful for importing tasks, but - /// otherwise should be avoided in favor of `new_task`. If the task already exists, this - /// does nothing and returns the existing task. - pub fn import_task_with_uuid(&mut self, uuid: Uuid) -> Result { - self.add_undo_point(false)?; - let taskmap = self.taskdb.apply(SyncOp::Create { uuid })?; - let depmap = self.dependency_map(false)?; - Ok(Task::new(uuid, taskmap, depmap)) - } - - /// Delete a task. The task must exist. Note that this is different from setting status to - /// Deleted; this is the final purge of the task. This is not a public method as deletion - /// should only occur through expiration. - fn delete_task(&mut self, uuid: Uuid) -> Result<()> { - self.add_undo_point(false)?; - self.taskdb.apply(SyncOp::Delete { uuid })?; - trace!("task {} deleted", uuid); - Ok(()) - } - - /// Synchronize this replica against the given server. The working set is rebuilt after - /// this occurs, but without renumbering, so any newly-pending tasks should appear in - /// the working set. - /// - /// If `avoid_snapshots` is true, the sync operations produces a snapshot only when the server - /// indicate it is urgent (snapshot urgency "high"). This allows time for other replicas to - /// create a snapshot before this one does. - /// - /// Set this to true on systems more constrained in CPU, memory, or bandwidth than a typical desktop - /// system - pub fn sync(&mut self, server: &mut Box, avoid_snapshots: bool) -> Result<()> { - self.taskdb - .sync(server, avoid_snapshots) - .context("Failed to synchronize with server")?; - self.rebuild_working_set(false) - .context("Failed to rebuild working set after sync")?; - Ok(()) - } - - /// Return undo local operations until the most recent UndoPoint, returning an empty Vec if there are no - /// local operations to undo. - pub fn get_undo_ops(&mut self) -> Result> { - self.taskdb.get_undo_ops() - } - - /// Undo local operations in storage, returning a boolean indicating success. - pub fn commit_undo_ops(&mut self, undo_ops: Vec) -> Result { - self.taskdb.commit_undo_ops(undo_ops) - } - - /// Rebuild this replica's working set, based on whether tasks are pending or not. If - /// `renumber` is true, then existing tasks may be moved to new working-set indices; in any - /// case, on completion all pending and recurring tasks are in the working set and all tasks - /// with other statuses are not. - pub fn rebuild_working_set(&mut self, renumber: bool) -> Result<()> { - let pending = String::from(Status::Pending.to_taskmap()); - let recurring = String::from(Status::Recurring.to_taskmap()); - self.taskdb.rebuild_working_set( - |t| { - if let Some(st) = t.get("status") { - st == &pending || st == &recurring - } else { - false - } - }, - renumber, - )?; - Ok(()) - } - - /// Expire old, deleted tasks. - /// - /// Expiration entails removal of tasks from the replica. Any modifications that occur after - /// the deletion (such as operations synchronized from other replicas) will do nothing. - /// - /// Tasks are eligible for expiration when they have status Deleted and have not been modified - /// for 180 days (about six months). Note that completed tasks are not eligible. - pub fn expire_tasks(&mut self) -> Result<()> { - let six_mos_ago = Utc::now() - Duration::days(180); - self.all_tasks()? - .iter() - .filter(|(_, t)| t.get_status() == Status::Deleted) - .filter(|(_, t)| { - if let Some(m) = t.get_modified() { - m < six_mos_ago - } else { - false - } - }) - .try_for_each(|(u, _)| self.delete_task(*u))?; - Ok(()) - } - - /// Add an UndoPoint, if one has not already been added by this Replica. This occurs - /// automatically when a change is made. The `force` flag allows forcing a new UndoPoint - /// even if one has already been created by this Replica, and may be useful when a Replica - /// instance is held for a long time and used to apply more than one user-visible change. - pub fn add_undo_point(&mut self, force: bool) -> Result<()> { - if force || !self.added_undo_point { - self.taskdb.add_undo_point()?; - self.added_undo_point = true; - } - Ok(()) - } - - /// Get the number of operations local to this replica and not yet synchronized to the server. - pub fn num_local_operations(&mut self) -> Result { - self.taskdb.num_operations() - } - - /// Get the number of undo points available (number of times `undo` will succeed). - pub fn num_undo_points(&mut self) -> Result { - self.taskdb.num_undo_points() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::storage::ReplicaOp; - use crate::task::Status; - use chrono::TimeZone; - use pretty_assertions::assert_eq; - use std::collections::HashSet; - use uuid::Uuid; - - #[test] - fn new_task() { - let mut rep = Replica::new_inmemory(); - - let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); - assert_eq!(t.get_description(), String::from("a task")); - assert_eq!(t.get_status(), Status::Pending); - assert!(t.get_modified().is_some()); - } - - #[test] - fn modify_task() { - let mut rep = Replica::new_inmemory(); - - let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); - - let mut t = t.into_mut(&mut rep); - t.set_description(String::from("past tense")).unwrap(); - t.set_status(Status::Completed).unwrap(); - // check that values have changed on the TaskMut - assert_eq!(t.get_description(), "past tense"); - assert_eq!(t.get_status(), Status::Completed); - - // check that values have changed after into_immut - let t = t.into_immut(); - assert_eq!(t.get_description(), "past tense"); - assert_eq!(t.get_status(), Status::Completed); - - // check that values have changed in storage, too - let t = rep.get_task(t.get_uuid()).unwrap().unwrap(); - assert_eq!(t.get_description(), "past tense"); - assert_eq!(t.get_status(), Status::Completed); - - // and check for the corresponding operations, cleaning out the timestamps - // and modified properties as these are based on the current time - let now = Utc::now(); - let clean_op = |op: ReplicaOp| { - if let ReplicaOp::Update { - uuid, - property, - mut old_value, - mut value, - .. - } = op - { - // rewrite automatically-created dates to "just-now" for ease - // of testing - if property == "modified" || property == "end" || property == "entry" { - if value.is_some() { - value = Some("just-now".into()); - } - if old_value.is_some() { - old_value = Some("just-now".into()); - } - } - ReplicaOp::Update { - uuid, - property, - old_value, - value, - timestamp: now, - } - } else { - op - } - }; - assert_eq!( - rep.taskdb - .operations() - .drain(..) - .map(clean_op) - .collect::>(), - vec![ - ReplicaOp::UndoPoint, - ReplicaOp::Create { uuid: t.get_uuid() }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "modified".into(), - old_value: None, - value: Some("just-now".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "description".into(), - old_value: None, - value: Some("a task".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "status".into(), - old_value: None, - value: Some("pending".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "entry".into(), - old_value: None, - value: Some("just-now".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "modified".into(), - old_value: Some("just-now".into()), - value: Some("just-now".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "description".into(), - old_value: Some("a task".into()), - value: Some("past tense".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "end".into(), - old_value: None, - value: Some("just-now".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid: t.get_uuid(), - property: "status".into(), - old_value: Some("pending".into()), - value: Some("completed".into()), - timestamp: now, - }, - ] - ); - - // num_local_operations includes all but the undo point - assert_eq!(rep.num_local_operations().unwrap(), 9); - - // num_undo_points includes only the undo point - assert_eq!(rep.num_undo_points().unwrap(), 1); - } - - #[test] - fn delete_task() { - let mut rep = Replica::new_inmemory(); - - let t = rep.new_task(Status::Pending, "a task".into()).unwrap(); - let uuid = t.get_uuid(); - - rep.delete_task(uuid).unwrap(); - assert_eq!(rep.get_task(uuid).unwrap(), None); - } - - #[test] - fn get_and_modify() { - let mut rep = Replica::new_inmemory(); - - let t = rep - .new_task(Status::Pending, "another task".into()) - .unwrap(); - let uuid = t.get_uuid(); - - let t = rep.get_task(uuid).unwrap().unwrap(); - assert_eq!(t.get_description(), String::from("another task")); - - let mut t = t.into_mut(&mut rep); - t.set_status(Status::Deleted).unwrap(); - t.set_description("gone".into()).unwrap(); - - let t = rep.get_task(uuid).unwrap().unwrap(); - assert_eq!(t.get_status(), Status::Deleted); - assert_eq!(t.get_description(), "gone"); - - rep.rebuild_working_set(true).unwrap(); - - let ws = rep.working_set().unwrap(); - assert!(ws.by_uuid(t.get_uuid()).is_none()); - } - - #[test] - fn rebuild_working_set_includes_recurring() { - let mut rep = Replica::new_inmemory(); - - let t = rep - .new_task(Status::Completed, "another task".into()) - .unwrap(); - let uuid = t.get_uuid(); - - let t = rep.get_task(uuid).unwrap().unwrap(); - { - let mut t = t.into_mut(&mut rep); - t.set_status(Status::Recurring).unwrap(); - } - - rep.rebuild_working_set(true).unwrap(); - - let ws = rep.working_set().unwrap(); - assert!(ws.by_uuid(uuid).is_some()); - } - - #[test] - fn new_pending_adds_to_working_set() { - let mut rep = Replica::new_inmemory(); - - let t = rep - .new_task(Status::Pending, "to-be-pending".into()) - .unwrap(); - let uuid = t.get_uuid(); - - let ws = rep.working_set().unwrap(); - assert_eq!(ws.len(), 1); // only one non-none value - assert!(ws.by_index(0).is_none()); - assert_eq!(ws.by_index(1), Some(uuid)); - - let ws = rep.working_set().unwrap(); - assert_eq!(ws.by_uuid(t.get_uuid()), Some(1)); - } - - #[test] - fn new_recurring_adds_to_working_set() { - let mut rep = Replica::new_inmemory(); - - let t = rep - .new_task(Status::Recurring, "to-be-recurring".into()) - .unwrap(); - let uuid = t.get_uuid(); - - let ws = rep.working_set().unwrap(); - assert_eq!(ws.len(), 1); // only one non-none value - assert!(ws.by_index(0).is_none()); - assert_eq!(ws.by_index(1), Some(uuid)); - - let ws = rep.working_set().unwrap(); - assert_eq!(ws.by_uuid(t.get_uuid()), Some(1)); - } - - #[test] - fn get_does_not_exist() { - let mut rep = Replica::new_inmemory(); - let uuid = Uuid::new_v4(); - assert_eq!(rep.get_task(uuid).unwrap(), None); - } - - #[test] - fn expire() { - let mut rep = Replica::new_inmemory(); - let mut t; - - rep.new_task(Status::Pending, "keeper 1".into()).unwrap(); - rep.new_task(Status::Completed, "keeper 2".into()).unwrap(); - - t = rep.new_task(Status::Deleted, "keeper 3".into()).unwrap(); - { - let mut t = t.into_mut(&mut rep); - // set entry, with modification set as a side-effect - t.set_entry(Some(Utc::now())).unwrap(); - } - - t = rep.new_task(Status::Deleted, "goner".into()).unwrap(); - { - let mut t = t.into_mut(&mut rep); - t.set_modified(Utc.ymd(1980, 1, 1).and_hms(0, 0, 0)) - .unwrap(); - } - - rep.expire_tasks().unwrap(); - - for (_, t) in rep.all_tasks().unwrap() { - println!("got task {}", t.get_description()); - assert!(t.get_description().starts_with("keeper")); - } - } - - #[test] - fn dependency_map() { - let mut rep = Replica::new_inmemory(); - - let mut tasks = vec![]; - for _ in 0..4 { - tasks.push(rep.new_task(Status::Pending, "t".into()).unwrap()); - } - - let uuids: Vec<_> = tasks.iter().map(|t| t.get_uuid()).collect(); - - // t[3] depends on t[2], and t[1] - { - let mut t = tasks.pop().unwrap().into_mut(&mut rep); - t.add_dependency(uuids[2]).unwrap(); - t.add_dependency(uuids[1]).unwrap(); - } - - // t[2] depends on t[0] - { - let mut t = tasks.pop().unwrap().into_mut(&mut rep); - t.add_dependency(uuids[0]).unwrap(); - } - - // t[1] depends on t[0] - { - let mut t = tasks.pop().unwrap().into_mut(&mut rep); - t.add_dependency(uuids[0]).unwrap(); - } - - // generate the dependency map, forcing an update based on the newly-added - // dependencies - let dm = rep.dependency_map(true).unwrap(); - - assert_eq!( - dm.dependencies(uuids[3]).collect::>(), - set![uuids[1], uuids[2]] - ); - assert_eq!( - dm.dependencies(uuids[2]).collect::>(), - set![uuids[0]] - ); - assert_eq!( - dm.dependencies(uuids[1]).collect::>(), - set![uuids[0]] - ); - assert_eq!(dm.dependencies(uuids[0]).collect::>(), set![]); - - assert_eq!(dm.dependents(uuids[3]).collect::>(), set![]); - assert_eq!( - dm.dependents(uuids[2]).collect::>(), - set![uuids[3]] - ); - assert_eq!( - dm.dependents(uuids[1]).collect::>(), - set![uuids[3]] - ); - assert_eq!( - dm.dependents(uuids[0]).collect::>(), - set![uuids[1], uuids[2]] - ); - - // mark t[0] as done, removing it from the working set - rep.get_task(uuids[0]) - .unwrap() - .unwrap() - .into_mut(&mut rep) - .done() - .unwrap(); - let dm = rep.dependency_map(true).unwrap(); - - assert_eq!( - dm.dependencies(uuids[3]).collect::>(), - set![uuids[1], uuids[2]] - ); - assert_eq!(dm.dependencies(uuids[2]).collect::>(), set![]); - assert_eq!(dm.dependencies(uuids[1]).collect::>(), set![]); - assert_eq!(dm.dependents(uuids[0]).collect::>(), set![]); - } -} diff --git a/taskchampion/taskchampion/src/server/cloud/gcp.rs b/taskchampion/taskchampion/src/server/cloud/gcp.rs deleted file mode 100644 index a53ad6e48..000000000 --- a/taskchampion/taskchampion/src/server/cloud/gcp.rs +++ /dev/null @@ -1,407 +0,0 @@ -use super::service::{ObjectInfo, Service}; -use crate::errors::Result; -use google_cloud_storage::client::google_cloud_auth::credentials::CredentialsFile; -use google_cloud_storage::client::{Client, ClientConfig}; -use google_cloud_storage::http::error::ErrorResponse; -use google_cloud_storage::http::Error as GcsError; -use google_cloud_storage::http::{self, objects}; -use tokio::runtime::Runtime; - -/// A [`Service`] implementation based on the Google Cloud Storage service. -pub(in crate::server) struct GcpService { - client: Client, - rt: Runtime, - bucket: String, -} - -/// Determine whether the given result contains an HTTP error with the given code. -fn is_http_error(query: u16, res: &std::result::Result) -> bool { - match res { - // Errors from RPC's. - Err(GcsError::Response(ErrorResponse { code, .. })) => *code == query, - // Errors from reqwest (downloads, uploads). - Err(GcsError::HttpClient(e)) => e.status().map(|s| s.as_u16()) == Some(query), - _ => false, - } -} - -impl GcpService { - pub(in crate::server) fn new(bucket: String, credential_path: Option) -> Result { - let rt = Runtime::new()?; - - let credentialpathstring = credential_path.clone().unwrap(); - let config: ClientConfig = if credential_path.unwrap() == "" { - rt.block_on(ClientConfig::default().with_auth())? - } else { - let credentials = rt.block_on(CredentialsFile::new_from_file(credentialpathstring))?; - rt.block_on(ClientConfig::default().with_credentials(credentials))? - }; - - Ok(Self { - client: Client::new(config), - rt, - bucket, - }) - } -} - -impl Service for GcpService { - fn put(&mut self, name: &[u8], value: &[u8]) -> Result<()> { - let name = String::from_utf8(name.to_vec()).expect("non-UTF8 object name"); - let upload_type = objects::upload::UploadType::Simple(objects::upload::Media::new(name)); - self.rt.block_on(self.client.upload_object( - &objects::upload::UploadObjectRequest { - bucket: self.bucket.clone(), - ..Default::default() - }, - value.to_vec(), - &upload_type, - ))?; - Ok(()) - } - - fn get(&mut self, name: &[u8]) -> Result>> { - let name = String::from_utf8(name.to_vec()).expect("non-UTF8 object name"); - let download_res = self.rt.block_on(self.client.download_object( - &objects::get::GetObjectRequest { - bucket: self.bucket.clone(), - object: name, - ..Default::default() - }, - &objects::download::Range::default(), - )); - if is_http_error(404, &download_res) { - Ok(None) - } else { - Ok(Some(download_res?)) - } - } - - fn del(&mut self, name: &[u8]) -> Result<()> { - let name = String::from_utf8(name.to_vec()).expect("non-UTF8 object name"); - let del_res = self.rt.block_on(self.client.delete_object( - &objects::delete::DeleteObjectRequest { - bucket: self.bucket.clone(), - object: name, - ..Default::default() - }, - )); - if !is_http_error(404, &del_res) { - del_res?; - } - Ok(()) - } - - fn list<'a>(&'a mut self, prefix: &[u8]) -> Box> + 'a> { - let prefix = String::from_utf8(prefix.to_vec()).expect("non-UTF8 object prefix"); - Box::new(ObjectIterator { - service: self, - prefix, - last_response: None, - next_index: 0, - }) - } - - fn compare_and_swap( - &mut self, - name: &[u8], - existing_value: Option>, - new_value: Vec, - ) -> Result { - let name = String::from_utf8(name.to_vec()).expect("non-UTF8 object name"); - let get_res = self - .rt - .block_on(self.client.get_object(&objects::get::GetObjectRequest { - bucket: self.bucket.clone(), - object: name.clone(), - ..Default::default() - })); - // Determine the object's generation. See https://cloud.google.com/storage/docs/metadata#generation-number - let generation = if is_http_error(404, &get_res) { - // If a value was expected, that expectation has not been met. - if existing_value.is_some() { - return Ok(false); - } - // Generation 0 indicates that the object does not yet exist. - 0 - } else { - get_res?.generation - }; - - // If the file existed, then verify its contents. - if generation > 0 { - let data = self.rt.block_on(self.client.download_object( - &objects::get::GetObjectRequest { - bucket: self.bucket.clone(), - object: name.clone(), - // Fetch the same generation. - generation: Some(generation), - ..Default::default() - }, - &objects::download::Range::default(), - ))?; - if Some(data) != existing_value { - return Ok(false); - } - } - - // Finally, put the new value with a condition that the generation hasn't changed. - let upload_type = objects::upload::UploadType::Simple(objects::upload::Media::new(name)); - let upload_res = self.rt.block_on(self.client.upload_object( - &objects::upload::UploadObjectRequest { - bucket: self.bucket.clone(), - if_generation_match: Some(generation), - ..Default::default() - }, - new_value.to_vec(), - &upload_type, - )); - if is_http_error(412, &upload_res) { - // A 412 indicates the precondition was not satisfied: the given generation - // is no longer the latest. - Ok(false) - } else { - upload_res?; - Ok(true) - } - } -} - -/// An Iterator returning names of objects from `list_objects`. -/// -/// This handles response pagination by fetching one page at a time. -struct ObjectIterator<'a> { - service: &'a mut GcpService, - prefix: String, - last_response: Option, - next_index: usize, -} - -impl<'a> ObjectIterator<'a> { - fn fetch_batch(&mut self) -> Result<()> { - let mut page_token = None; - if let Some(ref resp) = self.last_response { - page_token = resp.next_page_token.clone(); - } - self.last_response = Some(self.service.rt.block_on(self.service.client.list_objects( - &objects::list::ListObjectsRequest { - bucket: self.service.bucket.clone(), - prefix: Some(self.prefix.clone()), - page_token, - #[cfg(test)] // For testing, use a small page size. - max_results: Some(6), - ..Default::default() - }, - ))?); - self.next_index = 0; - Ok(()) - } -} - -impl<'a> Iterator for ObjectIterator<'a> { - type Item = Result; - fn next(&mut self) -> Option { - // If the iterator is just starting, fetch the first response. - if self.last_response.is_none() { - if let Err(e) = self.fetch_batch() { - return Some(Err(e)); - } - } - if let Some(ref result) = self.last_response { - if let Some(ref items) = result.items { - if self.next_index < items.len() { - // Return a result from the existing response. - let obj = &items[self.next_index]; - self.next_index += 1; - // It's unclear when `time_created` would be None, so default to 0 in that case - // or when the timestamp is not a valid u64 (before 1970). - let creation = obj.time_created.map(|t| t.unix_timestamp()).unwrap_or(0); - let creation: u64 = creation.try_into().unwrap_or(0); - return Some(Ok(ObjectInfo { - name: obj.name.as_bytes().to_vec(), - creation, - })); - } else if result.next_page_token.is_some() { - // Fetch the next page and try again. - if let Err(e) = self.fetch_batch() { - return Some(Err(e)); - } - return self.next(); - } - } - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use uuid::Uuid; - - /// Make a service if `GCP_TEST_BUCKET` is set, as well as a function to put a unique prefix on - /// an object name, so that tests do not interfere with one another. - /// - /// Set up this bucket with a lifecyle policy to delete objects with age > 1 day. While passing - /// tests should correctly clean up after themselves, failing tests may leave objects in the - /// bucket. - /// - /// When the environment variable is not set, this returns false and the test does not run. - /// Note that the Rust test runner will still show "ok" for the test, as there is no way to - /// indicate anything else. - fn make_service() -> Option<(GcpService, impl Fn(&str) -> Vec)> { - let Ok(bucket) = std::env::var("GCP_TEST_BUCKET") else { - return None; - }; - - let Ok(credential_path) = std::env::var("GCP_TEST_CREDENTIAL_PATH") else { - return None; - }; - - let prefix = Uuid::new_v4(); - Some(( - GcpService::new(bucket, Some(credential_path)).unwrap(), - move |n: &_| format!("{}-{}", prefix.as_simple(), n).into_bytes(), - )) - } - - #[test] - fn put_and_get() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - svc.put(&pfx("testy"), b"foo").unwrap(); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, Some(b"foo".to_vec())); - - // Clean up. - svc.del(&pfx("testy")).unwrap(); - } - - #[test] - fn get_missing() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, None); - } - - #[test] - fn del() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - svc.put(&pfx("testy"), b"data").unwrap(); - svc.del(&pfx("testy")).unwrap(); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, None); - } - - #[test] - fn del_missing() { - // Deleting an object that does not exist is not an error. - let Some((mut svc, pfx)) = make_service() else { - return; - }; - - assert!(svc.del(&pfx("testy")).is_ok()); - } - - #[test] - fn list() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - let mut names: Vec<_> = (0..20).map(|i| pfx(&format!("pp-{i:02}"))).collect(); - names.sort(); - // Create 20 objects that will be listed. - for n in &names { - svc.put(n, b"data").unwrap(); - } - // And another object that should not be included in the list. - svc.put(&pfx("xxx"), b"data").unwrap(); - - let got_objects: Vec<_> = svc.list(&pfx("pp-")).collect::>().unwrap(); - let mut got_names: Vec<_> = got_objects.into_iter().map(|oi| oi.name).collect(); - got_names.sort(); - assert_eq!(got_names, names); - - // Clean up. - for n in got_names { - svc.del(&n).unwrap(); - } - svc.del(&pfx("xxx")).unwrap(); - } - - #[test] - fn compare_and_swap_create() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - - assert!(svc - .compare_and_swap(&pfx("testy"), None, b"bar".to_vec()) - .unwrap()); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, Some(b"bar".to_vec())); - - // Clean up. - svc.del(&pfx("testy")).unwrap(); - } - - #[test] - fn compare_and_swap_matches() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - - // Create the existing file, with two generations. - svc.put(&pfx("testy"), b"foo1").unwrap(); - svc.put(&pfx("testy"), b"foo2").unwrap(); - assert!(svc - .compare_and_swap(&pfx("testy"), Some(b"foo2".to_vec()), b"bar".to_vec()) - .unwrap()); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, Some(b"bar".to_vec())); - - // Clean up. - svc.del(&pfx("testy")).unwrap(); - } - - #[test] - fn compare_and_swap_expected_no_file() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - - svc.put(&pfx("testy"), b"foo1").unwrap(); - assert!(!svc - .compare_and_swap(&pfx("testy"), None, b"bar".to_vec()) - .unwrap()); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, Some(b"foo1".to_vec())); - - // Clean up. - svc.del(&pfx("testy")).unwrap(); - } - - #[test] - fn compare_and_swap_mismatch() { - let Some((mut svc, pfx)) = make_service() else { - return; - }; - - // Create the existing file, with two generations. - svc.put(&pfx("testy"), b"foo1").unwrap(); - svc.put(&pfx("testy"), b"foo2").unwrap(); - assert!(!svc - .compare_and_swap(&pfx("testy"), Some(b"foo1".to_vec()), b"bar".to_vec()) - .unwrap()); - let got = svc.get(&pfx("testy")).unwrap(); - assert_eq!(got, Some(b"foo2".to_vec())); - - // Clean up. - svc.del(&pfx("testy")).unwrap(); - } -} diff --git a/taskchampion/taskchampion/src/server/cloud/mod.rs b/taskchampion/taskchampion/src/server/cloud/mod.rs deleted file mode 100644 index 970ced75c..000000000 --- a/taskchampion/taskchampion/src/server/cloud/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -/*! -* Support for cloud-service-backed sync. -* -* All of these operate using a similar approach, with specific patterns of object names. The -* process of adding a new version requires a compare-and-swap operation that sets a new version -* as the "latest" only if the existing "latest" has the expected value. This ensures a continuous -* chain of versions, even if multiple replicas attempt to sync at the same time. -*/ - -mod server; -mod service; - -pub(in crate::server) use server::CloudServer; - -#[cfg(feature = "server-gcp")] -pub(in crate::server) mod gcp; diff --git a/taskchampion/taskchampion/src/server/cloud/server.rs b/taskchampion/taskchampion/src/server/cloud/server.rs deleted file mode 100644 index 6aaee585b..000000000 --- a/taskchampion/taskchampion/src/server/cloud/server.rs +++ /dev/null @@ -1,1183 +0,0 @@ -use super::service::{ObjectInfo, Service}; -use crate::errors::{Error, Result}; -use crate::server::encryption::{Cryptor, Sealed, Unsealed}; -use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, - VersionId, -}; -use ring::rand; -use std::collections::{HashMap, HashSet}; -#[cfg(not(test))] -use std::time::{SystemTime, UNIX_EPOCH}; -use uuid::Uuid; - -/// Implement the Server trait for a cloud service implemented by [`Service`]. -/// -/// This type implements a TaskChampion server over a basic object-storage service. It encapsulates -/// all of the logic to ensure a linear sequence of versions, encrypt and decrypt data, and clean -/// up old data so that this can be supported on a variety of cloud services. -/// -/// ## Encryption -/// -/// The encryption scheme is described in `sync-protocol.md`. The salt value used for key -/// derivation is stored in "salt", which is created if it does not exist. Object names are not -/// encrypted, by the nature of key/value stores. Since the content of the "latest" object can -/// usually be inferred from object names, it, too, is not encrypted. -/// -/// ## Object Organization -/// -/// UUIDs emebedded in names and values appear in their "simple" form: lower-case hexadecimal with -/// no hyphens. -/// -/// Versions are stored as objects with name `v-PARENT-VERSION` where `PARENT` is the parent -/// version's UUID and `VERSION` is the version's UUID. The object value is the raw history -/// segment. These objects are created with simple `put` requests, as the name uniquely identifies -/// the content. -/// -/// The latest version is stored as an object with name "latest", containing the UUID of the latest -/// version. This file is updated with `compare_and_swap`. After a successful update of this -/// object, the version is considered committed. -/// -/// Since there are no strong constraints on creation of version objects, it is possible -/// to have multiple such files with the same `PARENT`. However, only one such object will be -/// contained in the chain of parent-child relationships beginning with the value in "latest". -/// All other objects are invalid and not visible outside this type. -/// -/// Snapshots are stored as objects with name `s-VERSION` where `VERSION` is the version at which -/// the snapshot was made. These objects are created with simple `put` requests, as any snapshot -/// for a given version is functionally equivalent to any other. -/// -/// ## Cleanup -/// -/// Cleanup of unnecessary data is performed probabalistically after `add_version`, although any -/// errors are ignored. -/// -/// - Any versions not reachable from "latest" and which cannot become "latest" are deleted. -/// - Any snapshots older than the most recent are deleted. -/// - Any versions older than [`MAX_VERSION_AGE_SECS`] which are incorporated into a snapshot -/// are deleted. -pub(in crate::server) struct CloudServer { - service: SVC, - - /// The Cryptor supporting encryption and decryption of objects in this server. - cryptor: Cryptor, - - /// The probability (0..255) that this run will perform cleanup. - cleanup_probability: u8, - - /// For testing, a function that is called in the middle of `add_version` to simulate - /// a concurrent change in the service. - #[cfg(test)] - add_version_intercept: Option, -} - -const LATEST: &[u8] = b"latest"; -const DEFAULT_CLEANUP_PROBABILITY: u8 = 13; // about 5% - -#[cfg(not(test))] -const MAX_VERSION_AGE_SECS: u64 = 3600 * 24 * 180; // about half a year - -fn version_to_bytes(v: VersionId) -> Vec { - v.as_simple().to_string().into_bytes() -} - -impl CloudServer { - pub(in crate::server) fn new(mut service: SVC, encryption_secret: Vec) -> Result { - let salt = Self::get_salt(&mut service)?; - let cryptor = Cryptor::new(salt, &encryption_secret.into())?; - Ok(Self { - service, - cryptor, - cleanup_probability: DEFAULT_CLEANUP_PROBABILITY, - #[cfg(test)] - add_version_intercept: None, - }) - } - - /// Get the salt value stored in the service, creating a new random one if necessary. - fn get_salt(service: &mut SVC) -> Result> { - const SALT_NAME: &[u8] = b"salt"; - loop { - if let Some(salt) = service.get(SALT_NAME)? { - return Ok(salt); - } - service.compare_and_swap(SALT_NAME, None, Cryptor::gen_salt()?)?; - } - } - - /// Generate an object name for the given parent and child versions. - fn version_name(parent_version_id: &VersionId, child_version_id: &VersionId) -> Vec { - format!( - "v-{}-{}", - parent_version_id.as_simple(), - child_version_id.as_simple() - ) - .into_bytes() - } - - /// Parse a version name as generated by `version_name`, returning None if the name does not - /// have a valid format. - fn parse_version_name(name: &[u8]) -> Option<(VersionId, VersionId)> { - if name.len() != 2 + 32 + 1 + 32 || !name.starts_with(b"v-") || name[2 + 32] != b'-' { - return None; - } - let Ok(parent_version_id) = VersionId::try_parse_ascii(&name[2..2 + 32]) else { - return None; - }; - let Ok(child_version_id) = VersionId::try_parse_ascii(&name[2 + 32 + 1..]) else { - return None; - }; - Some((parent_version_id, child_version_id)) - } - - /// Generate an object name for a snapshot at the given version. - fn snapshot_name(version_id: &VersionId) -> Vec { - format!("s-{}", version_id.as_simple()).into_bytes() - } - - /// Parse a snapshot name as generated by `snapshot_name`, returning None if the name does not - /// have a valid format. - fn parse_snapshot_name(name: &[u8]) -> Option { - if name.len() != 2 + 32 || !name.starts_with(b"s-") { - return None; - } - let Ok(version_id) = VersionId::try_parse_ascii(&name[2..2 + 32]) else { - return None; - }; - Some(version_id) - } - - /// Generate a random integer in (0..255) for use in probabalistic decisions. - fn randint(&self) -> Result { - use rand::SecureRandom; - let mut randint = [0u8]; - rand::SystemRandom::new() - .fill(&mut randint) - .map_err(|_| Error::Server("Random number generator failure".into()))?; - Ok(randint[0]) - } - - /// Get the version from "latest", or None if the object does not exist. This always fetches a fresh - /// value from storage. - fn get_latest(&mut self) -> Result> { - let Some(latest) = self.service.get(LATEST)? else { - return Ok(None); - }; - let latest = VersionId::try_parse_ascii(&latest) - .map_err(|_| Error::Server("'latest' object contains invalid data".into()))?; - Ok(Some(latest)) - } - - /// Get the possible child versions of the given parent version, based only on the object - /// names. - fn get_child_versions(&mut self, parent_version_id: &VersionId) -> Result> { - self.service - .list(format!("v-{}-", parent_version_id.as_simple()).as_bytes()) - .filter_map(|res| match res { - Ok(ObjectInfo { name, .. }) => { - if let Some((_, c)) = Self::parse_version_name(&name) { - Some(Ok(c)) - } else { - None - } - } - Err(e) => Some(Err(e)), - }) - .collect::>>() - } - - /// Determine the snapshot urgency. This is done probabalistically: - /// - High urgency approximately 1% of the time. - /// - Low urgency approximately 10% of the time. - fn snapshot_urgency(&self) -> Result { - let r = self.randint()?; - if r < 2 { - Ok(SnapshotUrgency::High) - } else if r < 25 { - Ok(SnapshotUrgency::Low) - } else { - Ok(SnapshotUrgency::None) - } - } - - /// Maybe call `cleanup` depending on `cleanup_probability`. - fn maybe_cleanup(&mut self) -> Result<()> { - if self.randint()? < self.cleanup_probability { - self.cleanup_probability = DEFAULT_CLEANUP_PROBABILITY; - self.cleanup() - } else { - Ok(()) - } - } - - /// Perform cleanup, deleting unnecessary data. - fn cleanup(&mut self) -> Result<()> { - // Construct a vector containing all (child, parent, creation) tuples - let mut versions = self - .service - .list(b"v-") - .filter_map(|res| match res { - Ok(ObjectInfo { name, creation }) => { - if let Some((p, c)) = Self::parse_version_name(&name) { - Some(Ok((c, p, creation))) - } else { - None - } - } - Err(e) => Some(Err(e)), - }) - .collect::>>()?; - versions.sort(); - - // Function to find the parent of a given child version in `versions`, taking - // advantage of having sorted the vector by child version ID. - let parent_of = |c| match versions.binary_search_by_key(&c, |tup| tup.0) { - Ok(idx) => Some(versions[idx].1), - Err(_) => None, - }; - - // Create chains mapping forward (parent -> child) and backward (child -> parent), starting - // at "latest". - let mut rev_chain = HashMap::new(); - let mut iterations = versions.len() + 1; // For cycle detection. - let latest = self.get_latest()?; - if let Some(mut c) = latest { - while let Some(p) = parent_of(c) { - rev_chain.insert(c, p); - c = p; - iterations -= 1; - if iterations == 0 { - return Err(Error::Server("Version cycle detected".into())); - } - } - } - - // Collect all versions older than MAX_VERSION_AGE_SECS - #[cfg(not(test))] - let age_threshold = { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map(|t| t.as_secs()) - .unwrap_or(0); - now.saturating_sub(MAX_VERSION_AGE_SECS) - }; - - // In testing, cutoff age is 1000. - #[cfg(test)] - let age_threshold = 1000; - - let old_versions: HashSet = versions - .iter() - .filter_map(|(c, _, creation)| { - if *creation < age_threshold { - Some(*c) - } else { - None - } - }) - .collect(); - - // Now, any pair not present in that chain can be deleted. However, another replica - // may be in the state where it has uploaded a version but not changed "latest" yet, - // so any pair with parent equal to latest is allowed to stay. - for (c, p, _) in versions { - if rev_chain.get(&c) != Some(&p) && Some(p) != latest { - self.service.del(&Self::version_name(&p, &c))?; - } - } - - // Collect a set of all snapshots. - let snapshots = self - .service - .list(b"s-") - .filter_map(|res| match res { - Ok(ObjectInfo { name, .. }) => Self::parse_snapshot_name(&name).map(Ok), - Err(e) => Some(Err(e)), - }) - .collect::>>()?; - - // Find the latest snapshot by iterating back from "latest". Note that this iteration is - // guaranteed not to be cyclical, as that was checked above. - let mut latest_snapshot = None; - if let Some(mut version) = latest { - loop { - if snapshots.contains(&version) { - latest_snapshot = Some(version); - break; - } - if let Some(v) = rev_chain.get(&version) { - version = *v; - } else { - break; - } - } - } - - // If there's a latest snapshot, delete all other snapshots. - let Some(latest_snapshot) = latest_snapshot else { - // If there's no snapshot, no further cleanup is possible. - return Ok(()); - }; - for version in snapshots { - if version != latest_snapshot { - self.service.del(&Self::snapshot_name(&version))?; - } - } - - // Now continue iterating backward from that version; any version in `old_versions` can be - // deleted. - let mut version = latest_snapshot; - while let Some(parent) = rev_chain.get(&version) { - if old_versions.contains(&version) { - self.service.del(&Self::version_name(parent, &version))?; - } - version = *parent; - } - - Ok(()) - } -} - -impl Server for CloudServer { - fn add_version( - &mut self, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Result<(AddVersionResult, SnapshotUrgency)> { - let latest = self.get_latest()?; - if let Some(l) = latest { - if l != parent_version_id { - return Ok(( - AddVersionResult::ExpectedParentVersion(l), - self.snapshot_urgency()?, - )); - } - } - - // Invent a new version ID and upload the version data. - let version_id = VersionId::new_v4(); - let new_name = Self::version_name(&parent_version_id, &version_id); - let sealed = self.cryptor.seal(Unsealed { - version_id, - payload: history_segment, - })?; - self.service.put(&new_name, sealed.as_ref())?; - - #[cfg(test)] - if let Some(f) = self.add_version_intercept { - f(&mut self.service); - } - - // Try to compare-and-swap this value into LATEST - let old_value = latest.map(version_to_bytes); - let new_value = version_to_bytes(version_id); - if !self - .service - .compare_and_swap(LATEST, old_value, new_value)? - { - // Delete the version data, since it was not latest. - self.service.del(&new_name)?; - let latest = self.get_latest()?; - let latest = latest.unwrap_or(Uuid::nil()); - return Ok(( - AddVersionResult::ExpectedParentVersion(latest), - self.snapshot_urgency()?, - )); - } - - // Attempt a cleanup, but ignore errors. - let _ = self.maybe_cleanup(); - - Ok((AddVersionResult::Ok(version_id), self.snapshot_urgency()?)) - } - - fn get_child_version(&mut self, parent_version_id: VersionId) -> Result { - // The `get_child_versions` function will usually return only one child version for a - // parent, in which case the work is easy. Otherwise, if there are several possible - // children, only one of those will lead to `latest`, and importantly the others will not - // have their own children. So we can detect the "true" child as the one that is equal to - // "latest" or has children. - let version_id = match &(self.get_child_versions(&parent_version_id)?)[..] { - [] => return Ok(GetVersionResult::NoSuchVersion), - [child] => *child, - children => { - // There are some extra version objects, so a cleanup is warranted. - self.cleanup_probability = 255; - let latest = self.get_latest()?; - let mut true_child = None; - for child in children { - if Some(*child) == latest { - true_child = Some(*child); - break; - } - } - if true_child.is_none() { - for child in children { - if !self.get_child_versions(child)?.is_empty() { - true_child = Some(*child) - } - } - } - match true_child { - Some(true_child) => true_child, - None => return Ok(GetVersionResult::NoSuchVersion), - } - } - }; - - let Some(sealed) = self - .service - .get(&Self::version_name(&parent_version_id, &version_id))? - else { - // This really shouldn't happen, since the chain was derived from object names, but - // perhaps the object was deleted. - return Ok(GetVersionResult::NoSuchVersion); - }; - let unsealed = self.cryptor.unseal(Sealed { - version_id, - payload: sealed, - })?; - Ok(GetVersionResult::Version { - version_id, - parent_version_id, - history_segment: unsealed.into(), - }) - } - - fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> Result<()> { - let name = Self::snapshot_name(&version_id); - let sealed = self.cryptor.seal(Unsealed { - version_id, - payload: snapshot, - })?; - self.service.put(&name, sealed.as_ref())?; - Ok(()) - } - - fn get_snapshot(&mut self) -> Result> { - // Pick the first snapshot we find. - let Some(name) = self.service.list(b"s-").next() else { - return Ok(None); - }; - let ObjectInfo { name, .. } = name?; - let Some(version_id) = Self::parse_snapshot_name(&name) else { - return Ok(None); - }; - let Some(payload) = self.service.get(&name)? else { - return Ok(None); - }; - let unsealed = self.cryptor.unseal(Sealed { - version_id, - payload, - })?; - Ok(Some((version_id, unsealed.payload))) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::server::NIL_VERSION_ID; - - /// A simple in-memory service for testing. All insertions via Service methods occur at time - /// `INSERTION_TIME`. All versions older that 1000 are considered "old". - #[derive(Clone)] - struct MockService(HashMap, (u64, Vec)>); - - const INSERTION_TIME: u64 = 9999999999; - - impl MockService { - fn new() -> Self { - let mut map = HashMap::new(); - // Use a fixed salt for consistent results - map.insert(b"salt".to_vec(), (0, b"abcdefghabcdefgh".to_vec())); - Self(map) - } - } - - impl Service for MockService { - fn put(&mut self, name: &[u8], value: &[u8]) -> Result<()> { - self.0 - .insert(name.to_vec(), (INSERTION_TIME, value.to_vec())); - Ok(()) - } - - fn get(&mut self, name: &[u8]) -> Result>> { - Ok(self.0.get(name).map(|(_, data)| data.clone())) - } - - fn del(&mut self, name: &[u8]) -> Result<()> { - self.0.remove(name); - Ok(()) - } - - fn compare_and_swap( - &mut self, - name: &[u8], - existing_value: Option>, - new_value: Vec, - ) -> Result { - if self.0.get(name).map(|(_, d)| d) == existing_value.as_ref() { - self.0.insert(name.to_vec(), (INSERTION_TIME, new_value)); - return Ok(true); - } - Ok(false) - } - - fn list<'a>( - &'a mut self, - prefix: &[u8], - ) -> Box> + 'a> { - let prefix = prefix.to_vec(); - Box::new( - self.0 - .iter() - .filter(move |(k, _)| k.starts_with(&prefix)) - .map(|(k, (t, _))| { - Ok(ObjectInfo { - name: k.to_vec(), - creation: *t, - }) - }), - ) - } - } - - impl std::fmt::Debug for MockService { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_map() - .entries( - self.0 - .iter() - .map(|(k, v)| (std::str::from_utf8(k).unwrap(), v)), - ) - .finish() - } - } - - // Add some testing utilities to CloudServer. - impl CloudServer { - fn mock_add_version( - &mut self, - parent: VersionId, - child: VersionId, - creation: u64, - data: &[u8], - ) { - let name = Self::version_name(&parent, &child); - let sealed = self - .cryptor - .seal(Unsealed { - version_id: child, - payload: data.into(), - }) - .unwrap(); - self.service.0.insert(name, (creation, sealed.into())); - } - - fn mock_add_snapshot(&mut self, version: VersionId, creation: u64, snapshot: &[u8]) { - let name = Self::snapshot_name(&version); - let sealed = self - .cryptor - .seal(Unsealed { - version_id: version, - payload: snapshot.into(), - }) - .unwrap(); - self.service.0.insert(name, (creation, sealed.into())); - } - - fn mock_set_latest(&mut self, latest: VersionId) { - let latest = version_to_bytes(latest); - self.service - .0 - .insert(LATEST.to_vec(), (INSERTION_TIME, latest)); - } - - /// Create a copy of this server without any data; used for creating a MockService - /// to compare to with `assert_eq!` - fn empty_clone(&self) -> Self { - Self { - cryptor: self.cryptor.clone(), - cleanup_probability: 0, - service: MockService::new(), - add_version_intercept: None, - } - } - - /// Get a decrypted, string-y copy of the data in the HashMap. - fn unencrypted(&self) -> HashMap { - self.service - .0 - .iter() - .map(|(k, v)| { - let kstr = String::from_utf8(k.to_vec()).unwrap(); - if kstr == "latest" { - return (kstr, (v.0, String::from_utf8(v.1.to_vec()).unwrap())); - } - - let version_id; - if let Some((_, v)) = Self::parse_version_name(k) { - version_id = v; - } else if let Some(v) = Self::parse_snapshot_name(k) { - version_id = v; - } else { - return (kstr, (v.0, format!("{:?}", v.1))); - } - - let unsealed = self - .cryptor - .unseal(Sealed { - version_id, - payload: v.1.to_vec(), - }) - .unwrap(); - let vstr = String::from_utf8(unsealed.into()).unwrap(); - (kstr, (v.0, vstr)) - }) - .collect() - } - } - impl Clone for CloudServer { - fn clone(&self) -> Self { - Self { - cryptor: self.cryptor.clone(), - cleanup_probability: self.cleanup_probability, - service: self.service.clone(), - add_version_intercept: None, - } - } - } - - const SECRET: &[u8] = b"testing"; - - fn make_server() -> CloudServer { - let mut server = CloudServer::new(MockService::new(), SECRET.into()).unwrap(); - // Prevent cleanup during tests. - server.cleanup_probability = 0; - server - } - - #[test] - fn version_name() { - let p = Uuid::parse_str("a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8").unwrap(); - let c = Uuid::parse_str("adcf4e350fa54e4aaf9d3f20f3ba5a32").unwrap(); - assert_eq!( - CloudServer::::version_name(&p, &c), - b"v-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8-adcf4e350fa54e4aaf9d3f20f3ba5a32" - ); - } - - #[test] - fn version_name_round_trip() { - let p = Uuid::new_v4(); - let c = Uuid::new_v4(); - assert_eq!( - CloudServer::::parse_version_name( - &CloudServer::::version_name(&p, &c) - ), - Some((p, c)) - ); - } - - #[test] - fn parse_version_name_bad_prefix() { - assert_eq!( - CloudServer::::parse_version_name( - b"X-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8-adcf4e350fa54e4aaf9d3f20f3ba5a32" - ), - None - ); - } - - #[test] - fn parse_version_name_bad_separator() { - assert_eq!( - CloudServer::::parse_version_name( - b"v-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8xadcf4e350fa54e4aaf9d3f20f3ba5a32" - ), - None - ); - } - - #[test] - fn parse_version_name_too_short() { - assert_eq!( - CloudServer::::parse_version_name( - b"v-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8-adcf4e350fa54e4aaf9d3f20f3ba5a3" - ), - None - ); - } - - #[test] - fn parse_version_name_too_long() { - assert_eq!( - CloudServer::::parse_version_name( - b"v-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8-adcf4e350fa54e4aaf9d3f20f3ba5a320" - ), - None - ); - } - - #[test] - fn snapshot_name_round_trip() { - let v = Uuid::new_v4(); - assert_eq!( - CloudServer::::parse_snapshot_name( - &CloudServer::::snapshot_name(&v) - ), - Some(v) - ); - } - - #[test] - fn parse_snapshot_name_invalid() { - assert_eq!( - CloudServer::::parse_snapshot_name(b"s-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"), - None - ); - } - - #[test] - fn parse_snapshot_name_bad_prefix() { - assert_eq!( - CloudServer::::parse_snapshot_name(b"s:a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"), - None - ); - } - - #[test] - fn parse_snapshot_name_too_short() { - assert_eq!( - CloudServer::::parse_snapshot_name(b"s-a1a2a3a4b1b2c1c2d1d2d3d4d5d6"), - None - ); - } - - #[test] - fn parse_snapshot_name_too_long() { - assert_eq!( - CloudServer::::parse_snapshot_name( - b"s-a1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8000" - ), - None - ); - } - - #[test] - fn get_salt_existing() { - let mut service = MockService::new(); - assert_eq!( - CloudServer::::get_salt(&mut service).unwrap(), - b"abcdefghabcdefgh".to_vec() - ); - } - - #[test] - fn get_salt_create() { - let mut service = MockService::new(); - service.del(b"salt").unwrap(); - let got_salt = CloudServer::::get_salt(&mut service).unwrap(); - let salt_obj = service.get(b"salt").unwrap().unwrap(); - assert_eq!(got_salt, salt_obj); - } - - #[test] - fn get_latest_empty() { - let mut server = make_server(); - assert_eq!(server.get_latest().unwrap(), None); - } - - #[test] - fn get_latest_exists() { - let mut server = make_server(); - let latest = Uuid::new_v4(); - server.mock_set_latest(latest); - assert_eq!(server.get_latest().unwrap(), Some(latest)); - } - - #[test] - fn get_latest_invalid() { - let mut server = make_server(); - server - .service - .0 - .insert(LATEST.to_vec(), (999, b"not-a-uuid".to_vec())); - assert!(server.get_latest().is_err()); - } - - #[test] - fn get_child_versions_empty() { - let mut server = make_server(); - assert_eq!(server.get_child_versions(&Uuid::new_v4()).unwrap(), vec![]); - } - - #[test] - fn get_child_versions_single() { - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v2, v1, 1000, b"first"); - assert_eq!(server.get_child_versions(&v1).unwrap(), vec![]); - assert_eq!(server.get_child_versions(&v2).unwrap(), vec![v1]); - } - - #[test] - fn get_child_versions_multiple() { - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v3, v1, 1000, b"first"); - server.mock_add_version(v3, v2, 1000, b"second"); - assert_eq!(server.get_child_versions(&v1).unwrap(), vec![]); - assert_eq!(server.get_child_versions(&v2).unwrap(), vec![]); - let versions = server.get_child_versions(&v3).unwrap(); - assert!(versions == vec![v1, v2] || versions == vec![v2, v1]); - } - - #[test] - fn add_version_empty() { - let mut server = make_server(); - let parent = Uuid::new_v4(); - let (res, _) = server.add_version(parent, b"history".to_vec()).unwrap(); - assert!(matches!(res, AddVersionResult::Ok(_))); - } - - #[test] - fn add_version_good() { - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"first"); - server.mock_set_latest(v2); - - let (res, _) = server.add_version(v2, b"history".to_vec()).unwrap(); - let AddVersionResult::Ok(new_version) = res else { - panic!("expected OK"); - }; - - let mut expected = server.empty_clone(); - expected.mock_add_version(v1, v2, 1000, b"first"); - expected.mock_add_version(v2, new_version, INSERTION_TIME, b"history"); - expected.mock_set_latest(new_version); - - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn add_version_not_latest() { - // The `add_version` method does nothing if the version is not latest. - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"first"); - server.mock_set_latest(v2); - - let expected = server.clone(); - - let (res, _) = server.add_version(v1, b"history".to_vec()).unwrap(); - assert_eq!(res, AddVersionResult::ExpectedParentVersion(v2)); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn add_version_not_latest_race() { - // The `add_version` function effectively checks twice for a conflict: once by just - // fetching "latest", returning early if the value is not as expected; and once in the - // compare-and-swap. This test uses `add_version_intercept` to force the first check to - // succeed and the second test to fail. - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - const V3: Uuid = Uuid::max(); - server.mock_add_version(v1, v2, 1000, b"first"); - server.mock_add_version(v2, V3, 1000, b"second"); - server.mock_set_latest(v2); - server.add_version_intercept = Some(|service| { - service.put(LATEST, &version_to_bytes(V3)).unwrap(); - }); - - let mut expected = server.empty_clone(); - expected.mock_add_version(v1, v2, 1000, b"first"); - expected.mock_add_version(v2, V3, 1000, b"second"); - expected.mock_set_latest(V3); // updated by the intercept - - assert_ne!(server.unencrypted(), expected.unencrypted()); - let (res, _) = server.add_version(v2, b"history".to_vec()).unwrap(); - assert_eq!(res, AddVersionResult::ExpectedParentVersion(V3)); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn add_version_unknown() { - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"first"); - server.mock_set_latest(v2); - - let expected = server.clone(); - - let (res, _) = server - .add_version(Uuid::new_v4(), b"history".to_vec()) - .unwrap(); - assert_eq!(res, AddVersionResult::ExpectedParentVersion(v2)); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn get_child_version_empty() { - let mut server = make_server(); - assert_eq!( - server.get_child_version(Uuid::new_v4()).unwrap(), - GetVersionResult::NoSuchVersion - ); - } - - #[test] - fn get_child_version_single() { - let mut server = make_server(); - let (v1, v2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v2, v1, 1000, b"first"); - assert_eq!( - server.get_child_version(v1).unwrap(), - GetVersionResult::NoSuchVersion - ); - assert_eq!( - server.get_child_version(v2).unwrap(), - GetVersionResult::Version { - version_id: v1, - parent_version_id: v2, - history_segment: b"first".to_vec(), - } - ); - } - - #[test] - fn get_child_version_multiple() { - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let (vx, vy, vz) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v1, vx, 1000, b"false start x"); - server.mock_add_version(v1, vy, 1000, b"false start y"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_add_version(v2, vz, 1000, b"false start z"); - server.mock_set_latest(v3); - assert_eq!( - server.get_child_version(v1).unwrap(), - GetVersionResult::Version { - version_id: v2, - parent_version_id: v1, - history_segment: b"second".to_vec(), - } - ); - assert_eq!( - server.get_child_version(v2).unwrap(), - GetVersionResult::Version { - version_id: v3, - parent_version_id: v2, - history_segment: b"third".to_vec(), - } - ); - assert_eq!( - server.get_child_version(v3).unwrap(), - GetVersionResult::NoSuchVersion - ); - } - - #[test] - fn cleanup_empty() { - let mut server = make_server(); - server.cleanup().unwrap(); - } - - #[test] - fn cleanup_linear() { - // Test that cleanup does nothing for a linear version history with a snapshot at the - // oldest version. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(NIL_VERSION_ID, v1, 1000, b"first"); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_add_snapshot(v1, 1000, b"snap 1"); - server.mock_set_latest(v3); - - let expected = server.clone(); - - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_cycle() { - // When a cycle is present, cleanup succeeds and makes no changes. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v3, v1, 1000, b"first"); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_set_latest(v3); - - let expected = server.clone(); - - assert!(server.cleanup().is_err()); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_extra_branches() { - // Cleanup deletes extra branches in the versions. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let (vx, vy) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v1, vx, 1000, b"false start x"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_add_version(v2, vy, 1000, b"false start y"); - server.mock_set_latest(v3); - - let mut expected = server.empty_clone(); - expected.mock_add_version(v1, v2, 1000, b"second"); - expected.mock_add_version(v2, v3, 1000, b"third"); - expected.mock_set_latest(v3); - - assert_ne!(server.unencrypted(), expected.unencrypted()); - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_extra_snapshots() { - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let vy = Uuid::new_v4(); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_add_version(v2, vy, 1000, b"false start y"); - server.mock_add_snapshot(v1, 1000, b"snap 1"); - server.mock_add_snapshot(v2, 1000, b"snap 2"); - server.mock_add_snapshot(vy, 1000, b"snap y"); - server.mock_set_latest(v3); - - let mut expected = server.empty_clone(); - expected.mock_add_version(v1, v2, 1000, b"second"); - expected.mock_add_version(v2, v3, 1000, b"third"); - expected.mock_add_snapshot(v2, 1000, b"snap 2"); - expected.mock_set_latest(v3); - - assert_ne!(server.unencrypted(), expected.unencrypted()); - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_old_versions_no_snapshot() { - // If there are old versions ,but no snapshot, nothing is cleaned up. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 200, b"second"); - server.mock_add_version(v2, v3, 300, b"third"); - server.mock_set_latest(v3); - - let expected = server.clone(); - - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_old_versions_with_snapshot() { - // If there are old versions that are also older than a snapshot, they are - // cleaned up. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let (v4, v5, v6) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 200, b"second"); - server.mock_add_version(v2, v3, 300, b"third"); - server.mock_add_version(v3, v4, 1400, b"fourth"); - server.mock_add_version(v4, v5, 1500, b"fifth"); - server.mock_add_snapshot(v5, 1501, b"snap 1"); - server.mock_add_version(v5, v6, 1600, b"sixth"); - server.mock_set_latest(v6); - - let mut expected = server.empty_clone(); - expected.mock_add_version(v3, v4, 1400, b"fourth"); // Not old enough to be deleted. - expected.mock_add_version(v4, v5, 1500, b"fifth"); - expected.mock_add_snapshot(v5, 1501, b"snap 1"); - expected.mock_add_version(v5, v6, 1600, b"sixth"); - expected.mock_set_latest(v6); - - assert_ne!(server.unencrypted(), expected.unencrypted()); - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_old_versions_newer_than_snapshot() { - // Old versions that are newer than the latest snapshot are not cleaned up. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let (v4, v5, v6) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 200, b"second"); - server.mock_add_version(v2, v3, 300, b"third"); - server.mock_add_snapshot(v3, 301, b"snap 1"); - server.mock_add_version(v3, v4, 400, b"fourth"); - server.mock_add_version(v4, v5, 500, b"fifth"); - server.mock_add_version(v5, v6, 600, b"sixth"); - server.mock_set_latest(v6); - - let mut expected = server.empty_clone(); - expected.mock_add_snapshot(v3, 301, b"snap 1"); - expected.mock_add_version(v3, v4, 400, b"fourth"); - expected.mock_add_version(v4, v5, 500, b"fifth"); - expected.mock_add_version(v5, v6, 600, b"sixth"); - expected.mock_set_latest(v6); - - assert_ne!(server.unencrypted(), expected.unencrypted()); - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn cleanup_children_of_latest() { - // New versions that are children of the latest version are not cleaned up. - let mut server = make_server(); - let (v1, v2, v3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4()); - let (vnew1, vnew2) = (Uuid::new_v4(), Uuid::new_v4()); - server.mock_add_version(v1, v2, 1000, b"second"); - server.mock_add_version(v2, v3, 1000, b"third"); - server.mock_add_version(v3, vnew1, 1000, b"new 1"); - server.mock_add_version(v3, vnew2, 1000, b"new 2"); - // Two replicas are adding new versions, but v3 is still latest. - server.mock_set_latest(v3); - - let expected = server.clone(); - - server.cleanup().unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn add_snapshot() { - let mut server = make_server(); - let v = Uuid::new_v4(); - - let mut expected = server.empty_clone(); - expected.mock_add_snapshot(v, INSERTION_TIME, b"SNAP"); - - assert_ne!(server.unencrypted(), expected.unencrypted()); - server.add_snapshot(v, b"SNAP".to_vec()).unwrap(); - assert_eq!(server.unencrypted(), expected.unencrypted()); - } - - #[test] - fn get_snapshot_missing() { - let mut server = make_server(); - assert_eq!(server.get_snapshot().unwrap(), None); - } - - #[test] - fn get_snapshot_present() { - let mut server = make_server(); - let v = Uuid::new_v4(); - server.mock_add_snapshot(v, 1000, b"SNAP"); - assert_eq!(server.get_snapshot().unwrap(), Some((v, b"SNAP".to_vec()))); - } -} diff --git a/taskchampion/taskchampion/src/server/cloud/service.rs b/taskchampion/taskchampion/src/server/cloud/service.rs deleted file mode 100644 index 84b1c24ef..000000000 --- a/taskchampion/taskchampion/src/server/cloud/service.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::errors::Result; - -/// Information about an object as returned from `Service::list` -pub(in crate::server) struct ObjectInfo { - /// Name of the object. - pub(in crate::server) name: Vec, - /// Creation time of the object, in seconds since the UNIX epoch. - pub(in crate::server) creation: u64, -} - -/// An abstraction of a cloud-storage service. -/// -/// The underlying cloud storage is assumed to be a map from object names to object values, -/// similar to a HashMap, with the addition of a compare-and-swap operation. Object names -/// are always simple strings from the character set `[a-zA-Z0-9-]`, no more than 100 characters -/// in length. -pub(in crate::server) trait Service { - /// Put an object into cloud storage. If the object exists, it is overwritten. - fn put(&mut self, name: &[u8], value: &[u8]) -> Result<()>; - - /// Get an object from cloud storage, or None if the object does not exist. - fn get(&mut self, name: &[u8]) -> Result>>; - - /// Delete an object. Does nothing if the object does not exist. - fn del(&mut self, name: &[u8]) -> Result<()>; - - /// Enumerate objects with the given prefix. - fn list<'a>(&'a mut self, prefix: &[u8]) -> Box> + 'a>; - - /// Compare the existing object's value with `existing_value`, and replace with `new_value` - /// only if the values match. Returns true if the replacement occurred. - fn compare_and_swap( - &mut self, - name: &[u8], - existing_value: Option>, - new_value: Vec, - ) -> Result; -} diff --git a/taskchampion/taskchampion/src/server/config.rs b/taskchampion/taskchampion/src/server/config.rs deleted file mode 100644 index 20e0e6598..000000000 --- a/taskchampion/taskchampion/src/server/config.rs +++ /dev/null @@ -1,74 +0,0 @@ -use super::types::Server; -use crate::errors::Result; -#[cfg(feature = "server-gcp")] -use crate::server::cloud::gcp::GcpService; -#[cfg(feature = "cloud")] -use crate::server::cloud::CloudServer; -use crate::server::local::LocalServer; -#[cfg(feature = "server-sync")] -use crate::server::sync::SyncServer; -use std::path::PathBuf; -#[cfg(feature = "server-sync")] -use uuid::Uuid; - -/// The configuration for a replica's access to a sync server. -pub enum ServerConfig { - /// A local task database, for situations with a single replica. - Local { - /// Path containing the server's DB - server_dir: PathBuf, - }, - /// A remote taskchampion-sync-server instance - #[cfg(feature = "server-sync")] - Remote { - /// Sync server "origin"; a URL with schema and hostname but no path or trailing `/` - origin: String, - - /// Client ID to identify and authenticate this replica to the server - client_id: Uuid, - - /// Private encryption secret used to encrypt all data sent to the server. This can - /// be any suitably un-guessable string of bytes. - encryption_secret: Vec, - }, - /// A remote taskchampion-sync-server instance - #[cfg(feature = "server-gcp")] - Gcp { - /// Bucket in which to store the task data. This bucket must not be used for any other - /// purpose. - bucket: String, - /// Path to a GCP credential file, in JSON format. This is required for GCP access incase - /// some other application already makes use of Application Default Credentials. - /// See https://cloud.google.com/docs/authentication#service-accounts for more details. - /// See https://cloud.google.com/iam/docs/keys-create-delete for instructions on how to - /// create a service account key. - credential_path: Option, - /// Private encryption secret used to encrypt all data sent to the server. This can - /// be any suitably un-guessable string of bytes. - encryption_secret: Vec, - }, -} - -impl ServerConfig { - /// Get a server based on this configuration - pub fn into_server(self) -> Result> { - Ok(match self { - ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?), - #[cfg(feature = "server-sync")] - ServerConfig::Remote { - origin, - client_id, - encryption_secret, - } => Box::new(SyncServer::new(origin, client_id, encryption_secret)?), - #[cfg(feature = "server-gcp")] - ServerConfig::Gcp { - bucket, - credential_path, - encryption_secret, - } => Box::new(CloudServer::new( - GcpService::new(bucket, credential_path)?, - encryption_secret, - )?), - }) - } -} diff --git a/taskchampion/taskchampion/src/server/encryption.rs b/taskchampion/taskchampion/src/server/encryption.rs deleted file mode 100644 index 23fa17a9c..000000000 --- a/taskchampion/taskchampion/src/server/encryption.rs +++ /dev/null @@ -1,414 +0,0 @@ -/// This module implements the encryption specified in the sync-protocol -/// document. -use crate::errors::{Error, Result}; -use ring::{aead, pbkdf2, rand, rand::SecureRandom}; -use uuid::Uuid; - -const PBKDF2_ITERATIONS: u32 = 600000; -const ENVELOPE_VERSION: u8 = 1; -const AAD_LEN: usize = 17; -const TASK_APP_ID: u8 = 1; - -/// An Cryptor stores a secret and allows sealing and unsealing. It derives a key from the secret, -/// which takes a nontrivial amount of time, so it should be created once and re-used for the given -/// context. -#[derive(Clone)] -pub(super) struct Cryptor { - key: aead::LessSafeKey, - rng: rand::SystemRandom, -} - -impl Cryptor { - pub(super) fn new(salt: impl AsRef<[u8]>, secret: &Secret) -> Result { - Ok(Cryptor { - key: Self::derive_key(salt, secret)?, - rng: rand::SystemRandom::new(), - }) - } - - /// Generate a suitable random salt. - pub(super) fn gen_salt() -> Result> { - let rng = rand::SystemRandom::new(); - let mut salt = [0u8; 16]; - rng.fill(&mut salt) - .map_err(|_| anyhow::anyhow!("error generating random salt"))?; - Ok(salt.to_vec()) - } - - /// Derive a key as specified for version 1. Note that this may take 10s of ms. - fn derive_key(salt: impl AsRef<[u8]>, secret: &Secret) -> Result { - let mut key_bytes = vec![0u8; aead::CHACHA20_POLY1305.key_len()]; - pbkdf2::derive( - pbkdf2::PBKDF2_HMAC_SHA256, - std::num::NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(), - salt.as_ref(), - secret.as_ref(), - &mut key_bytes, - ); - - let unbound_key = aead::UnboundKey::new(&aead::CHACHA20_POLY1305, &key_bytes) - .map_err(|_| anyhow::anyhow!("error while creating AEAD key"))?; - Ok(aead::LessSafeKey::new(unbound_key)) - } - - /// Encrypt the given payload. - pub(super) fn seal(&self, payload: Unsealed) -> Result { - let Unsealed { - version_id, - mut payload, - } = payload; - - let mut nonce_buf = [0u8; aead::NONCE_LEN]; - self.rng - .fill(&mut nonce_buf) - .map_err(|_| anyhow::anyhow!("error generating random nonce"))?; - let nonce = aead::Nonce::assume_unique_for_key(nonce_buf); - - let aad = self.make_aad(version_id); - - let tag = self - .key - .seal_in_place_separate_tag(nonce, aad, &mut payload) - .map_err(|_| anyhow::anyhow!("error while sealing"))?; - payload.extend_from_slice(tag.as_ref()); - - let env = Envelope { - nonce: &nonce_buf, - payload: payload.as_ref(), - }; - - Ok(Sealed { - version_id, - payload: env.to_bytes(), - }) - } - - /// Decrypt the given payload, verifying it was created for the given version_id - pub(super) fn unseal(&self, payload: Sealed) -> Result { - let Sealed { - version_id, - payload, - } = payload; - - let env = Envelope::from_bytes(&payload)?; - - let mut nonce = [0u8; aead::NONCE_LEN]; - nonce.copy_from_slice(env.nonce); - let nonce = aead::Nonce::assume_unique_for_key(nonce); - let aad = self.make_aad(version_id); - - let mut payload = env.payload.to_vec(); - let plaintext = self - .key - .open_in_place(nonce, aad, payload.as_mut()) - .map_err(|_| anyhow::anyhow!("error while unsealing encrypted value"))?; - - Ok(Unsealed { - version_id, - payload: plaintext.to_vec(), - }) - } - - fn make_aad(&self, version_id: Uuid) -> aead::Aad<[u8; AAD_LEN]> { - let mut aad = [0u8; AAD_LEN]; - aad[0] = TASK_APP_ID; - aad[1..].copy_from_slice(version_id.as_bytes()); - aead::Aad::from(aad) - } -} - -/// Secret represents a secret key as used for encryption and decryption. -pub(super) struct Secret(pub(super) Vec); - -impl From> for Secret { - fn from(bytes: Vec) -> Self { - Self(bytes) - } -} - -impl AsRef<[u8]> for Secret { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// Envelope for the data stored on the server, containing the information -/// required to decrypt. -#[derive(Debug, PartialEq, Eq)] -struct Envelope<'a> { - nonce: &'a [u8], - payload: &'a [u8], -} - -impl<'a> Envelope<'a> { - fn from_bytes(buf: &'a [u8]) -> Result> { - if buf.len() <= 1 + aead::NONCE_LEN { - return Err(Error::Server(String::from("envelope is too small"))); - } - - let version = buf[0]; - if version != ENVELOPE_VERSION { - return Err(Error::Server(format!( - "unrecognized encryption envelope version {}", - version - ))); - } - - Ok(Envelope { - nonce: &buf[1..1 + aead::NONCE_LEN], - payload: &buf[1 + aead::NONCE_LEN..], - }) - } - - fn to_bytes(&self) -> Vec { - let mut buf = Vec::with_capacity(1 + self.nonce.len() + self.payload.len()); - - buf.push(ENVELOPE_VERSION); - buf.extend_from_slice(self.nonce); - buf.extend_from_slice(self.payload); - buf - } -} - -/// A unsealed payload with an attached version_id. The version_id is used to -/// validate the context of the payload on unsealing. -pub(super) struct Unsealed { - pub(super) version_id: Uuid, - pub(super) payload: Vec, -} - -impl From for Vec { - fn from(val: Unsealed) -> Self { - val.payload - } -} - -/// An encrypted payload -pub(super) struct Sealed { - pub(super) version_id: Uuid, - pub(super) payload: Vec, -} - -impl AsRef<[u8]> for Sealed { - fn as_ref(&self) -> &[u8] { - self.payload.as_ref() - } -} - -impl From for Vec { - fn from(val: Sealed) -> Self { - val.payload - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - fn make_salt() -> Vec { - Cryptor::gen_salt().unwrap() - } - - #[test] - fn envelope_round_trip() { - let env = Envelope { - nonce: &[2; 12], - payload: b"HELLO", - }; - - let bytes = env.to_bytes(); - let env2 = Envelope::from_bytes(&bytes).unwrap(); - assert_eq!(env, env2); - } - - #[test] - fn envelope_bad_version() { - let env = Envelope { - nonce: &[2; 12], - payload: b"HELLO", - }; - - let mut bytes = env.to_bytes(); - bytes[0] = 99; - assert!(Envelope::from_bytes(&bytes).is_err()); - } - - #[test] - fn envelope_too_short() { - let env = Envelope { - nonce: &[2; 12], - payload: b"HELLO", - }; - - let bytes = env.to_bytes(); - let bytes = &bytes[..10]; - assert!(Envelope::from_bytes(bytes).is_err()); - } - - #[test] - fn round_trip() { - let version_id = Uuid::new_v4(); - let payload = b"HISTORY REPEATS ITSELF".to_vec(); - - let secret = Secret(b"SEKRIT".to_vec()); - let cryptor = Cryptor::new(make_salt(), &secret).unwrap(); - - let unsealed = Unsealed { - version_id, - payload: payload.clone(), - }; - let sealed = cryptor.seal(unsealed).unwrap(); - let unsealed = cryptor.unseal(sealed).unwrap(); - - assert_eq!(unsealed.payload, payload); - assert_eq!(unsealed.version_id, version_id); - } - - #[test] - fn round_trip_bad_key() { - let version_id = Uuid::new_v4(); - let payload = b"HISTORY REPEATS ITSELF".to_vec(); - let salt = make_salt(); - - let secret = Secret(b"SEKRIT".to_vec()); - let cryptor = Cryptor::new(&salt, &secret).unwrap(); - - let unsealed = Unsealed { - version_id, - payload, - }; - let sealed = cryptor.seal(unsealed).unwrap(); - - let secret = Secret(b"DIFFERENT_SECRET".to_vec()); - let cryptor = Cryptor::new(&salt, &secret).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn round_trip_bad_version() { - let version_id = Uuid::new_v4(); - let payload = b"HISTORY REPEATS ITSELF".to_vec(); - - let secret = Secret(b"SEKRIT".to_vec()); - let cryptor = Cryptor::new(make_salt(), &secret).unwrap(); - - let unsealed = Unsealed { - version_id, - payload, - }; - let mut sealed = cryptor.seal(unsealed).unwrap(); - sealed.version_id = Uuid::new_v4(); // change the version_id - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn round_trip_bad_salt() { - let version_id = Uuid::new_v4(); - let payload = b"HISTORY REPEATS ITSELF".to_vec(); - - let secret = Secret(b"SEKRIT".to_vec()); - let cryptor = Cryptor::new(make_salt(), &secret).unwrap(); - - let unsealed = Unsealed { - version_id, - payload, - }; - let sealed = cryptor.seal(unsealed).unwrap(); - - let cryptor = Cryptor::new(make_salt(), &secret).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - mod externally_valid { - // validate data generated by generate-test-data.py. The intent is to - // validate that this format matches the specification by implementing - // the specification in a second language - use super::*; - use pretty_assertions::assert_eq; - - /// The values in generate-test-data.py - fn defaults() -> (Uuid, Vec, Vec) { - let version_id = Uuid::parse_str("b0517957-f912-4d49-8330-f612e73030c4").unwrap(); - let encryption_secret = b"b4a4e6b7b811eda1dc1a2693ded".to_vec(); - let client_id = Uuid::parse_str("0666d464-418a-4a08-ad53-6f15c78270cd").unwrap(); - let salt = client_id.as_bytes().to_vec(); - (version_id, salt, encryption_secret) - } - - #[test] - fn good() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-good.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - let unsealed = cryptor.unseal(sealed).unwrap(); - - assert_eq!(unsealed.payload, b"SUCCESS"); - assert_eq!(unsealed.version_id, version_id); - } - - #[test] - fn bad_version_id() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-bad-version-id.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn bad_salt() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-bad-client-id.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn bad_secret() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-bad-secret.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn bad_version() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-bad-version.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - - #[test] - fn bad_app_id() { - let (version_id, salt, encryption_secret) = defaults(); - let sealed = Sealed { - version_id, - payload: include_bytes!("test-bad-app-id.data").to_vec(), - }; - - let cryptor = Cryptor::new(salt, &Secret(encryption_secret)).unwrap(); - assert!(cryptor.unseal(sealed).is_err()); - } - } -} diff --git a/taskchampion/taskchampion/src/server/generate-test-data.py b/taskchampion/taskchampion/src/server/generate-test-data.py deleted file mode 100644 index d4f818190..000000000 --- a/taskchampion/taskchampion/src/server/generate-test-data.py +++ /dev/null @@ -1,77 +0,0 @@ -# This file generates test-encrypted.data. To run it: -# - pip install cryptography pbkdf2 -# - python taskchampion/taskchampion/src/server/generate-test-data.py taskchampion/taskchampion/src/server/ - -import os -import hashlib -import pbkdf2 -import secrets -import sys -import uuid - -from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305 - -# these values match values used in the rust tests -client_id = "0666d464-418a-4a08-ad53-6f15c78270cd" -encryption_secret = b"b4a4e6b7b811eda1dc1a2693ded" -version_id = "b0517957-f912-4d49-8330-f612e73030c4" - -def gen( - version_id=version_id, client_id=client_id, encryption_secret=encryption_secret, - app_id=1, version=1): - # first, generate the encryption key - salt = uuid.UUID(client_id).bytes - key = pbkdf2.PBKDF2( - encryption_secret, - salt, - digestmodule=hashlib.sha256, - iterations=600000, - ).read(32) - - # create a nonce - nonce = secrets.token_bytes(12) - - assert len(b"\x01") == 1 - # create the AAD - aad = b''.join([ - bytes([app_id]), - uuid.UUID(version_id).bytes, - ]) - - # encrypt using AEAD - chacha = ChaCha20Poly1305(key) - ciphertext = chacha.encrypt(nonce, b"SUCCESS", aad) - - # create the envelope - envelope = b''.join([ - bytes([version]), - nonce, - ciphertext, - ]) - - return envelope - - -def main(): - dir = sys.argv[1] - - with open(os.path.join(dir, 'test-good.data'), "wb") as f: - f.write(gen()) - - with open(os.path.join(dir, 'test-bad-version-id.data'), "wb") as f: - f.write(gen(version_id=uuid.uuid4().hex)) - - with open(os.path.join(dir, 'test-bad-client-id.data'), "wb") as f: - f.write(gen(client_id=uuid.uuid4().hex)) - - with open(os.path.join(dir, 'test-bad-secret.data'), "wb") as f: - f.write(gen(encryption_secret=b"xxxxxxxxxxxxxxxxxxxxx")) - - with open(os.path.join(dir, 'test-bad-version.data'), "wb") as f: - f.write(gen(version=99)) - - with open(os.path.join(dir, 'test-bad-app-id.data'), "wb") as f: - f.write(gen(app_id=99)) - - -main() diff --git a/taskchampion/taskchampion/src/server/local/mod.rs b/taskchampion/taskchampion/src/server/local/mod.rs deleted file mode 100644 index a5a4d2f98..000000000 --- a/taskchampion/taskchampion/src/server/local/mod.rs +++ /dev/null @@ -1,258 +0,0 @@ -use crate::errors::Result; -use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, - VersionId, NIL_VERSION_ID, -}; -use crate::storage::sqlite::StoredUuid; -use anyhow::Context; -use rusqlite::params; -use rusqlite::OptionalExtension; -use serde::{Deserialize, Serialize}; -use std::path::Path; -use uuid::Uuid; - -#[derive(Serialize, Deserialize, Debug)] -struct Version { - version_id: VersionId, - parent_version_id: VersionId, - history_segment: HistorySegment, -} - -pub struct LocalServer { - con: rusqlite::Connection, -} - -impl LocalServer { - fn txn(&mut self) -> Result { - let txn = self.con.transaction()?; - Ok(txn) - } - - /// A server which has no notion of clients, signatures, encryption, etc. - pub fn new>(directory: P) -> Result { - let db_file = directory - .as_ref() - .join("taskchampion-local-sync-server.sqlite3"); - let con = rusqlite::Connection::open(db_file)?; - - let queries = vec![ - "CREATE TABLE IF NOT EXISTS data (key STRING PRIMARY KEY, value STRING);", - "CREATE TABLE IF NOT EXISTS versions (version_id STRING PRIMARY KEY, parent_version_id STRING, data STRING);", - ]; - for q in queries { - con.execute(q, []).context("Creating table")?; - } - - Ok(LocalServer { con }) - } - - fn get_latest_version_id(&mut self) -> Result { - let t = self.txn()?; - let result: Option = t - .query_row( - "SELECT value FROM data WHERE key = 'latest_version_id' LIMIT 1", - rusqlite::params![], - |r| r.get(0), - ) - .optional()?; - Ok(result.map(|x| x.0).unwrap_or(NIL_VERSION_ID)) - } - - fn set_latest_version_id(&mut self, version_id: VersionId) -> Result<()> { - let t = self.txn()?; - t.execute( - "INSERT OR REPLACE INTO data (key, value) VALUES ('latest_version_id', ?)", - params![&StoredUuid(version_id)], - ) - .context("Update task query")?; - t.commit()?; - Ok(()) - } - - fn get_version_by_parent_version_id( - &mut self, - parent_version_id: VersionId, - ) -> Result> { - let t = self.txn()?; - let r = t.query_row( - "SELECT version_id, parent_version_id, data FROM versions WHERE parent_version_id = ?", - params![&StoredUuid(parent_version_id)], - |r| { - let version_id: StoredUuid = r.get("version_id")?; - let parent_version_id: StoredUuid = r.get("parent_version_id")?; - - Ok(Version{ - version_id: version_id.0, - parent_version_id: parent_version_id.0, - history_segment: r.get("data")?, - })} - ) - .optional() - .context("Get version query") - ?; - Ok(r) - } - - fn add_version_by_parent_version_id(&mut self, version: Version) -> Result<()> { - let t = self.txn()?; - t.execute( - "INSERT INTO versions (version_id, parent_version_id, data) VALUES (?, ?, ?)", - params![ - StoredUuid(version.version_id), - StoredUuid(version.parent_version_id), - version.history_segment - ], - )?; - t.commit()?; - Ok(()) - } -} - -impl Server for LocalServer { - // TODO: better transaction isolation for add_version (gets and sets should be in the same - // transaction) - - fn add_version( - &mut self, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Result<(AddVersionResult, SnapshotUrgency)> { - // no client lookup - // no signature validation - - // check the parent_version_id for linearity - let latest_version_id = self.get_latest_version_id()?; - if latest_version_id != NIL_VERSION_ID && parent_version_id != latest_version_id { - return Ok(( - AddVersionResult::ExpectedParentVersion(latest_version_id), - SnapshotUrgency::None, - )); - } - - // invent a new ID for this version - let version_id = Uuid::new_v4(); - - self.add_version_by_parent_version_id(Version { - version_id, - parent_version_id, - history_segment, - })?; - self.set_latest_version_id(version_id)?; - - Ok((AddVersionResult::Ok(version_id), SnapshotUrgency::None)) - } - - fn get_child_version(&mut self, parent_version_id: VersionId) -> Result { - if let Some(version) = self.get_version_by_parent_version_id(parent_version_id)? { - Ok(GetVersionResult::Version { - version_id: version.version_id, - parent_version_id: version.parent_version_id, - history_segment: version.history_segment, - }) - } else { - Ok(GetVersionResult::NoSuchVersion) - } - } - - fn add_snapshot(&mut self, _version_id: VersionId, _snapshot: Snapshot) -> Result<()> { - // the local server never requests a snapshot, so it should never get one - unreachable!() - } - - fn get_snapshot(&mut self) -> Result> { - Ok(None) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - use tempfile::TempDir; - - #[test] - fn test_empty() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut server = LocalServer::new(tmp_dir.path())?; - let child_version = server.get_child_version(NIL_VERSION_ID)?; - assert_eq!(child_version, GetVersionResult::NoSuchVersion); - Ok(()) - } - - #[test] - fn test_add_zero_base() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut server = LocalServer::new(tmp_dir.path())?; - let history = b"1234".to_vec(); - match server.add_version(NIL_VERSION_ID, history.clone())?.0 { - AddVersionResult::ExpectedParentVersion(_) => { - panic!("should have accepted the version") - } - AddVersionResult::Ok(version_id) => { - let new_version = server.get_child_version(NIL_VERSION_ID)?; - assert_eq!( - new_version, - GetVersionResult::Version { - version_id, - parent_version_id: NIL_VERSION_ID, - history_segment: history, - } - ); - } - } - - Ok(()) - } - - #[test] - fn test_add_nonzero_base() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut server = LocalServer::new(tmp_dir.path())?; - let history = b"1234".to_vec(); - let parent_version_id = Uuid::new_v4() as VersionId; - - // This is OK because the server has no latest_version_id yet - match server.add_version(parent_version_id, history.clone())?.0 { - AddVersionResult::ExpectedParentVersion(_) => { - panic!("should have accepted the version") - } - AddVersionResult::Ok(version_id) => { - let new_version = server.get_child_version(parent_version_id)?; - assert_eq!( - new_version, - GetVersionResult::Version { - version_id, - parent_version_id, - history_segment: history, - } - ); - } - } - - Ok(()) - } - - #[test] - fn test_add_nonzero_base_forbidden() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut server = LocalServer::new(tmp_dir.path())?; - let history = b"1234".to_vec(); - let parent_version_id = Uuid::new_v4() as VersionId; - - // add a version - if let (AddVersionResult::ExpectedParentVersion(_), SnapshotUrgency::None) = - server.add_version(parent_version_id, history.clone())? - { - panic!("should have accepted the version") - } - - // then add another, not based on that one - if let (AddVersionResult::Ok(_), SnapshotUrgency::None) = - server.add_version(parent_version_id, history)? - { - panic!("should not have accepted the version") - } - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/server/mod.rs b/taskchampion/taskchampion/src/server/mod.rs deleted file mode 100644 index caccdee02..000000000 --- a/taskchampion/taskchampion/src/server/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -/** - -This module defines the client interface to TaskChampion sync servers. -It defines a [trait](crate::server::Server) for servers, and implements both local and remote servers. - -Typical uses of this crate do not interact directly with this module; [`ServerConfig`](crate::ServerConfig) is sufficient. -However, users who wish to implement their own server interfaces can implement the traits defined here and pass the result to [`Replica`](crate::Replica). - -*/ - -#[cfg(test)] -pub(crate) mod test; - -mod config; -mod local; -mod op; -mod types; - -#[cfg(feature = "encryption")] -mod encryption; - -#[cfg(feature = "server-sync")] -mod sync; - -#[cfg(feature = "cloud")] -mod cloud; - -pub use config::ServerConfig; -pub use types::*; - -pub(crate) use op::SyncOp; diff --git a/taskchampion/taskchampion/src/server/op.rs b/taskchampion/taskchampion/src/server/op.rs deleted file mode 100644 index abd592d17..000000000 --- a/taskchampion/taskchampion/src/server/op.rs +++ /dev/null @@ -1,421 +0,0 @@ -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -/// A SyncOp defines a single change to the task database, that can be synchronized -/// via a server. -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] -pub enum SyncOp { - /// Create a new task. - /// - /// On application, if the task already exists, the operation does nothing. - Create { uuid: Uuid }, - - /// Delete an existing task. - /// - /// On application, if the task does not exist, the operation does nothing. - Delete { uuid: Uuid }, - - /// Update an existing task, setting the given property to the given value. If the value is - /// None, then the corresponding property is deleted. - /// - /// If the given task does not exist, the operation does nothing. - Update { - uuid: Uuid, - property: String, - value: Option, - timestamp: DateTime, - }, -} - -use SyncOp::*; - -impl SyncOp { - // Transform takes two operations A and B that happened concurrently and produces two - // operations A' and B' such that `apply(apply(S, A), B') = apply(apply(S, B), A')`. This - // function is used to serialize operations in a process similar to a Git "rebase". - // - // * - // / \ - // op1 / \ op2 - // / \ - // * * - // - // this function "completes the diamond: - // - // * * - // \ / - // op2' \ / op1' - // \ / - // * - // - // such that applying op2' after op1 has the same effect as applying op1' after op2. This - // allows two different systems which have already applied op1 and op2, respectively, and thus - // reached different states, to return to the same state by applying op2' and op1', - // respectively. - pub fn transform(operation1: SyncOp, operation2: SyncOp) -> (Option, Option) { - match (&operation1, &operation2) { - // Two creations or deletions of the same uuid reach the same state, so there's no need - // for any further operations to bring the state together. - (&Create { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), - (&Delete { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => (None, None), - - // Given a create and a delete of the same task, one of the operations is invalid: the - // create implies the task does not exist, but the delete implies it exists. Somewhat - // arbitrarily, we prefer the Create - (&Create { uuid: uuid1 }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - (&Delete { uuid: uuid1 }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - - // And again from an Update and a Create, prefer the Update - (&Update { uuid: uuid1, .. }, &Create { uuid: uuid2 }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - (&Create { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - - // Given a delete and an update, prefer the delete - (&Update { uuid: uuid1, .. }, &Delete { uuid: uuid2 }) if uuid1 == uuid2 => { - (None, Some(operation2)) - } - (&Delete { uuid: uuid1 }, &Update { uuid: uuid2, .. }) if uuid1 == uuid2 => { - (Some(operation1), None) - } - - // Two updates to the same property of the same task might conflict. - ( - Update { - uuid: uuid1, - property: property1, - value: value1, - timestamp: timestamp1, - }, - Update { - uuid: uuid2, - property: property2, - value: value2, - timestamp: timestamp2, - }, - ) if uuid1 == uuid2 && property1 == property2 => { - // if the value is the same, there's no conflict - if value1 == value2 { - (None, None) - } else if timestamp1 < timestamp2 { - // prefer the later modification - (None, Some(operation2)) - } else { - // prefer the later modification or, if the modifications are the same, - // just choose one of them - (Some(operation1), None) - } - } - - // anything else is not a conflict of any sort, so return the operations unchanged - (_, _) => (Some(operation1), Some(operation2)), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::errors::Result; - use crate::storage::InMemoryStorage; - use crate::taskdb::TaskDb; - use chrono::{Duration, Utc}; - use pretty_assertions::assert_eq; - use proptest::prelude::*; - - #[test] - fn test_json_create() -> Result<()> { - let uuid = Uuid::new_v4(); - let op = Create { uuid }; - let json = serde_json::to_string(&op)?; - assert_eq!(json, format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid)); - let deser: SyncOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_delete() -> Result<()> { - let uuid = Uuid::new_v4(); - let op = Delete { uuid }; - let json = serde_json::to_string(&op)?; - assert_eq!(json, format!(r#"{{"Delete":{{"uuid":"{}"}}}}"#, uuid)); - let deser: SyncOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_update() -> Result<()> { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - let op = Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp, - }; - - let json = serde_json::to_string(&op)?; - assert_eq!( - json, - format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","value":"false","timestamp":"{:?}"}}}}"#, - uuid, timestamp, - ) - ); - let deser: SyncOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_update_none() -> Result<()> { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - let op = Update { - uuid, - property: "abc".into(), - value: None, - timestamp, - }; - - let json = serde_json::to_string(&op)?; - assert_eq!( - json, - format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","value":null,"timestamp":"{:?}"}}}}"#, - uuid, timestamp, - ) - ); - let deser: SyncOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - fn test_transform( - setup: Option, - o1: SyncOp, - o2: SyncOp, - exp1p: Option, - exp2p: Option, - ) { - let (o1p, o2p) = SyncOp::transform(o1.clone(), o2.clone()); - assert_eq!((&o1p, &o2p), (&exp1p, &exp2p)); - - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. - let mut db1 = TaskDb::new_inmemory(); - if let Some(ref o) = setup { - db1.apply(o.clone()).unwrap(); - } - db1.apply(o1).unwrap(); - if let Some(o) = o2p { - db1.apply(o).unwrap(); - } - - let mut db2 = TaskDb::new_inmemory(); - if let Some(ref o) = setup { - db2.apply(o.clone()).unwrap(); - } - db2.apply(o2).unwrap(); - if let Some(o) = o1p { - db2.apply(o).unwrap(); - } - - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - - #[test] - fn test_unrelated_create() { - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - test_transform( - None, - Create { uuid: uuid1 }, - Create { uuid: uuid2 }, - Some(Create { uuid: uuid1 }), - Some(Create { uuid: uuid2 }), - ); - } - - #[test] - fn test_related_updates_different_props() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }, - Update { - uuid, - property: "def".into(), - value: Some("false".into()), - timestamp, - }, - Some(Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }), - Some(Update { - uuid, - property: "def".into(), - value: Some("false".into()), - timestamp, - }), - ); - } - - #[test] - fn test_related_updates_same_prop() { - let uuid = Uuid::new_v4(); - let timestamp1 = Utc::now(); - let timestamp2 = timestamp1 + Duration::seconds(10); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp: timestamp1, - }, - Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp: timestamp2, - }, - None, - Some(Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp: timestamp2, - }), - ); - } - - #[test] - fn test_related_updates_same_prop_same_time() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - test_transform( - Some(Create { uuid }), - Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }, - Update { - uuid, - property: "abc".into(), - value: Some("false".into()), - timestamp, - }, - Some(Update { - uuid, - property: "abc".into(), - value: Some("true".into()), - timestamp, - }), - None, - ); - } - - fn uuid_strategy() -> impl Strategy { - prop_oneof![ - Just(Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap()), - Just(Uuid::parse_str("56e0be07-c61f-494c-a54c-bdcfdd52d2a7").unwrap()), - Just(Uuid::parse_str("4b7ed904-f7b0-4293-8a10-ad452422c7b3").unwrap()), - Just(Uuid::parse_str("9bdd0546-07c8-4e1f-a9bc-9d6299f4773b").unwrap()), - ] - } - - fn operation_strategy() -> impl Strategy { - prop_oneof![ - uuid_strategy().prop_map(|uuid| Create { uuid }), - uuid_strategy().prop_map(|uuid| Delete { uuid }), - (uuid_strategy(), "(title|project|status)").prop_map(|(uuid, property)| { - Update { - uuid, - property, - value: Some("true".into()), - timestamp: Utc::now(), - } - }), - ] - } - - proptest! { - #![proptest_config(ProptestConfig { - cases: 1024, .. ProptestConfig::default() - })] - #[test] - // check that the two operation sequences have the same effect, enforcing the invariant of - // the transform function. - fn transform_invariant_holds(o1 in operation_strategy(), o2 in operation_strategy()) { - let (o1p, o2p) = SyncOp::transform(o1.clone(), o2.clone()); - - let mut db1 = TaskDb::new(Box::new(InMemoryStorage::new())); - let mut db2 = TaskDb::new(Box::new(InMemoryStorage::new())); - - // Ensure that any expected tasks already exist - if let Update{ uuid, .. } = o1 { - let _ = db1.apply(Create{uuid}); - let _ = db2.apply(Create{uuid}); - } - - if let Update{ uuid, .. } = o2 { - let _ = db1.apply(Create{uuid}); - let _ = db2.apply(Create{uuid}); - } - - if let Delete{ uuid } = o1 { - let _ = db1.apply(Create{uuid}); - let _ = db2.apply(Create{uuid}); - } - - if let Delete{ uuid } = o2 { - let _ = db1.apply(Create{uuid}); - let _ = db2.apply(Create{uuid}); - } - - // if applying the initial operations fail, that indicates the operation was invalid - // in the base state, so consider the case successful. - if db1.apply(o1).is_err() { - return Ok(()); - } - if db2.apply(o2).is_err() { - return Ok(()); - } - - if let Some(o) = o2p { - db1.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db1: {}", e).into()))?; - } - if let Some(o) = o1p { - db2.apply(o).map_err(|e| TestCaseError::Fail(format!("Applying to db2: {}", e).into()))?; - } - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - } - } -} diff --git a/taskchampion/taskchampion/src/server/sync/mod.rs b/taskchampion/taskchampion/src/server/sync/mod.rs deleted file mode 100644 index 3ae86aa23..000000000 --- a/taskchampion/taskchampion/src/server/sync/mod.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::errors::{Error, Result}; -use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, - VersionId, -}; -use std::time::Duration; -use url::Url; -use uuid::Uuid; - -use super::encryption::{Cryptor, Sealed, Secret, Unsealed}; - -pub struct SyncServer { - origin: String, - client_id: Uuid, - cryptor: Cryptor, - agent: ureq::Agent, -} - -/// The content-type for history segments (opaque blobs of bytes) -const HISTORY_SEGMENT_CONTENT_TYPE: &str = "application/vnd.taskchampion.history-segment"; - -/// The content-type for snapshots (opaque blobs of bytes) -const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot"; - -/// A SyncServer communicates with a sync server over HTTP. -impl SyncServer { - /// Construct a new SyncServer. The `origin` is the sync server's protocol and hostname - /// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to - /// identify this client to the server. Multiple replicas synchronizing the same task history - /// should use the same client_id. - pub fn new(origin: String, client_id: Uuid, encryption_secret: Vec) -> Result { - let origin = Url::parse(&origin) - .map_err(|_| Error::Server(format!("Could not parse {} as a URL", origin)))?; - if origin.path() != "/" { - return Err(Error::Server(format!( - "Server origin must have an empty path; got {}", - origin - ))); - } - Ok(SyncServer { - origin: origin.to_string(), - client_id, - cryptor: Cryptor::new(client_id, &Secret(encryption_secret.to_vec()))?, - agent: ureq::AgentBuilder::new() - .timeout_connect(Duration::from_secs(10)) - .timeout_read(Duration::from_secs(60)) - .build(), - }) - } -} - -/// Read a UUID-bearing header or fail trying -fn get_uuid_header(resp: &ureq::Response, name: &str) -> Result { - let value = resp - .header(name) - .ok_or_else(|| anyhow::anyhow!("Response does not have {} header", name))?; - let value = Uuid::parse_str(value) - .map_err(|e| anyhow::anyhow!("{} header is not a valid UUID: {}", name, e))?; - Ok(value) -} - -/// Read the X-Snapshot-Request header and return a SnapshotUrgency -fn get_snapshot_urgency(resp: &ureq::Response) -> SnapshotUrgency { - match resp.header("X-Snapshot-Request") { - None => SnapshotUrgency::None, - Some(hdr) => match hdr { - "urgency=low" => SnapshotUrgency::Low, - "urgency=high" => SnapshotUrgency::High, - _ => SnapshotUrgency::None, - }, - } -} - -fn sealed_from_resp(resp: ureq::Response, version_id: Uuid, content_type: &str) -> Result { - use std::io::Read; - if resp.header("Content-Type") == Some(content_type) { - let mut reader = resp.into_reader(); - let mut payload = vec![]; - reader.read_to_end(&mut payload)?; - Ok(Sealed { - version_id, - payload, - }) - } else { - Err(Error::Server(String::from( - "Response did not have expected content-type", - ))) - } -} - -impl Server for SyncServer { - fn add_version( - &mut self, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Result<(AddVersionResult, SnapshotUrgency)> { - let url = format!("{}v1/client/add-version/{}", self.origin, parent_version_id); - let unsealed = Unsealed { - version_id: parent_version_id, - payload: history_segment, - }; - let sealed = self.cryptor.seal(unsealed)?; - match self - .agent - .post(&url) - .set("Content-Type", HISTORY_SEGMENT_CONTENT_TYPE) - .set("X-Client-Id", &self.client_id.to_string()) - .send_bytes(sealed.as_ref()) - { - Ok(resp) => { - let version_id = get_uuid_header(&resp, "X-Version-Id")?; - Ok(( - AddVersionResult::Ok(version_id), - get_snapshot_urgency(&resp), - )) - } - Err(ureq::Error::Status(status, resp)) if status == 409 => { - let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; - Ok(( - AddVersionResult::ExpectedParentVersion(parent_version_id), - SnapshotUrgency::None, - )) - } - Err(err) => Err(err.into()), - } - } - - fn get_child_version(&mut self, parent_version_id: VersionId) -> Result { - let url = format!( - "{}v1/client/get-child-version/{}", - self.origin, parent_version_id - ); - match self - .agent - .get(&url) - .set("X-Client-Id", &self.client_id.to_string()) - .call() - { - Ok(resp) => { - let parent_version_id = get_uuid_header(&resp, "X-Parent-Version-Id")?; - let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let sealed = - sealed_from_resp(resp, parent_version_id, HISTORY_SEGMENT_CONTENT_TYPE)?; - let history_segment = self.cryptor.unseal(sealed)?.payload; - Ok(GetVersionResult::Version { - version_id, - parent_version_id, - history_segment, - }) - } - Err(ureq::Error::Status(status, _)) if status == 404 => { - Ok(GetVersionResult::NoSuchVersion) - } - Err(err) => Err(err.into()), - } - } - - fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> Result<()> { - let url = format!("{}v1/client/add-snapshot/{}", self.origin, version_id); - let unsealed = Unsealed { - version_id, - payload: snapshot, - }; - let sealed = self.cryptor.seal(unsealed)?; - Ok(self - .agent - .post(&url) - .set("Content-Type", SNAPSHOT_CONTENT_TYPE) - .set("X-Client-Id", &self.client_id.to_string()) - .send_bytes(sealed.as_ref()) - .map(|_| ())?) - } - - fn get_snapshot(&mut self) -> Result> { - let url = format!("{}v1/client/snapshot", self.origin); - match self - .agent - .get(&url) - .set("X-Client-Id", &self.client_id.to_string()) - .call() - { - Ok(resp) => { - let version_id = get_uuid_header(&resp, "X-Version-Id")?; - let sealed = sealed_from_resp(resp, version_id, SNAPSHOT_CONTENT_TYPE)?; - let snapshot = self.cryptor.unseal(sealed)?.payload; - Ok(Some((version_id, snapshot))) - } - Err(ureq::Error::Status(status, _)) if status == 404 => Ok(None), - Err(err) => Err(err.into()), - } - } -} diff --git a/taskchampion/taskchampion/src/server/test-bad-app-id.data b/taskchampion/taskchampion/src/server/test-bad-app-id.data deleted file mode 100644 index e0dbb5f69..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-app-id.data +++ /dev/null @@ -1,2 +0,0 @@ -INu -;Ɛ^9I걏ĉib \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-bad-client-id.data b/taskchampion/taskchampion/src/server/test-bad-client-id.data deleted file mode 100644 index 10885b533..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-client-id.data +++ /dev/null @@ -1 +0,0 @@ -hX.EI / B  \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-bad-secret.data b/taskchampion/taskchampion/src/server/test-bad-secret.data deleted file mode 100644 index 435585345..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-secret.data +++ /dev/null @@ -1 +0,0 @@ -Y|z JuѾKzF]bCx \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-bad-version-id.data b/taskchampion/taskchampion/src/server/test-bad-version-id.data deleted file mode 100644 index 3b14c54db..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-version-id.data +++ /dev/null @@ -1 +0,0 @@ -SsL|:=";ў5 JK= \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-bad-version.data b/taskchampion/taskchampion/src/server/test-bad-version.data deleted file mode 100644 index 7a1380c57..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-version.data +++ /dev/null @@ -1 +0,0 @@ -czy326LzA};B6@ \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-bad-version_id.data b/taskchampion/taskchampion/src/server/test-bad-version_id.data deleted file mode 100644 index 4a228ec4a..000000000 --- a/taskchampion/taskchampion/src/server/test-bad-version_id.data +++ /dev/null @@ -1,2 +0,0 @@ -B --3%j,*ߺ7꩖QKOFPZ \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test-good.data b/taskchampion/taskchampion/src/server/test-good.data deleted file mode 100644 index afe7678ba..000000000 --- a/taskchampion/taskchampion/src/server/test-good.data +++ /dev/null @@ -1 +0,0 @@ - t`&_)Ӊgr}-Zs \ No newline at end of file diff --git a/taskchampion/taskchampion/src/server/test.rs b/taskchampion/taskchampion/src/server/test.rs deleted file mode 100644 index 165fa557b..000000000 --- a/taskchampion/taskchampion/src/server/test.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::errors::Result; -use crate::server::{ - AddVersionResult, GetVersionResult, HistorySegment, Server, Snapshot, SnapshotUrgency, - VersionId, NIL_VERSION_ID, -}; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use uuid::Uuid; - -struct Version { - version_id: VersionId, - parent_version_id: VersionId, - history_segment: HistorySegment, -} - -/// TestServer implements the Server trait with a test implementation. -#[derive(Clone)] -pub(crate) struct TestServer(Arc>); - -pub(crate) struct Inner { - latest_version_id: VersionId, - // NOTE: indexed by parent_version_id! - versions: HashMap, - snapshot_urgency: SnapshotUrgency, - snapshot: Option<(VersionId, Snapshot)>, -} - -impl TestServer { - /// A test server has no notion of clients, signatures, encryption, etc. - pub(crate) fn new() -> TestServer { - TestServer(Arc::new(Mutex::new(Inner { - latest_version_id: NIL_VERSION_ID, - versions: HashMap::new(), - snapshot_urgency: SnapshotUrgency::None, - snapshot: None, - }))) - } - // feel free to add any test utility functions here - - /// Get a boxed Server implementation referring to this TestServer - pub(crate) fn server(&self) -> Box { - Box::new(self.clone()) - } - - pub(crate) fn set_snapshot_urgency(&self, urgency: SnapshotUrgency) { - let mut inner = self.0.lock().unwrap(); - inner.snapshot_urgency = urgency; - } - - /// Get the latest snapshot added to this server - pub(crate) fn snapshot(&self) -> Option<(VersionId, Snapshot)> { - let inner = self.0.lock().unwrap(); - inner.snapshot.as_ref().cloned() - } - - /// Delete a version from storage - pub(crate) fn delete_version(&mut self, parent_version_id: VersionId) { - let mut inner = self.0.lock().unwrap(); - inner.versions.remove(&parent_version_id); - } -} - -impl Server for TestServer { - /// Add a new version. If the given version number is incorrect, this responds with the - /// appropriate version and expects the caller to try again. - fn add_version( - &mut self, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Result<(AddVersionResult, SnapshotUrgency)> { - let mut inner = self.0.lock().unwrap(); - - // no client lookup - // no signature validation - - // check the parent_version_id for linearity - if inner.latest_version_id != NIL_VERSION_ID && parent_version_id != inner.latest_version_id - { - return Ok(( - AddVersionResult::ExpectedParentVersion(inner.latest_version_id), - SnapshotUrgency::None, - )); - } - - // invent a new ID for this version - let version_id = Uuid::new_v4(); - - inner.versions.insert( - parent_version_id, - Version { - version_id, - parent_version_id, - history_segment, - }, - ); - inner.latest_version_id = version_id; - - // reply with the configured urgency and reset it to None - let urgency = inner.snapshot_urgency; - inner.snapshot_urgency = SnapshotUrgency::None; - Ok((AddVersionResult::Ok(version_id), urgency)) - } - - /// Get a vector of all versions after `since_version` - fn get_child_version(&mut self, parent_version_id: VersionId) -> Result { - let inner = self.0.lock().unwrap(); - - if let Some(version) = inner.versions.get(&parent_version_id) { - Ok(GetVersionResult::Version { - version_id: version.version_id, - parent_version_id: version.parent_version_id, - history_segment: version.history_segment.clone(), - }) - } else { - Ok(GetVersionResult::NoSuchVersion) - } - } - - fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> Result<()> { - let mut inner = self.0.lock().unwrap(); - - // test implementation -- does not perform any validation - inner.snapshot = Some((version_id, snapshot)); - Ok(()) - } - - fn get_snapshot(&mut self) -> Result> { - let inner = self.0.lock().unwrap(); - Ok(inner.snapshot.clone()) - } -} diff --git a/taskchampion/taskchampion/src/server/types.rs b/taskchampion/taskchampion/src/server/types.rs deleted file mode 100644 index 8929ae23b..000000000 --- a/taskchampion/taskchampion/src/server/types.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::errors::Result; -use uuid::Uuid; - -/// Versions are referred to with UUIDs. -pub type VersionId = Uuid; - -/// The distinguished value for "no version" -pub const NIL_VERSION_ID: VersionId = Uuid::nil(); - -/// A segment in the history of this task database, in the form of a sequence of operations. This -/// data is pre-encoded, and from the protocol level appears as a sequence of bytes. -pub type HistorySegment = Vec; - -/// A snapshot of the state of the task database. This is encoded by the taskdb implementation -/// and treated as a sequence of bytes by the server implementation. -pub type Snapshot = Vec; - -/// AddVersionResult is the response type from [`crate::server::Server::add_version`]. -#[derive(Debug, PartialEq, Eq)] -pub enum AddVersionResult { - /// OK, version added with the given ID - Ok(VersionId), - /// Rejected; expected a version with the given parent version - ExpectedParentVersion(VersionId), -} - -/// SnapshotUrgency indicates how much the server would like this replica to send a snapshot. -#[derive(PartialEq, Debug, Clone, Copy, Eq, PartialOrd, Ord)] -pub enum SnapshotUrgency { - /// Don't need a snapshot right now. - None, - /// A snapshot would be good, but can wait for other replicas to provide it. - Low, - /// A snapshot is needed right now. - High, -} - -/// A version as downloaded from the server -#[derive(Debug, PartialEq, Eq)] -pub enum GetVersionResult { - /// No such version exists - NoSuchVersion, - - /// The requested version - Version { - version_id: VersionId, - parent_version_id: VersionId, - history_segment: HistorySegment, - }, -} - -/// A value implementing this trait can act as a server against which a replica can sync. -pub trait Server { - /// Add a new version. - /// - /// This must ensure that the new version is the only version with the given - /// `parent_version_id`, and that all versions form a single parent-child chain. Inductively, - /// this means that if there are any versions on the server, then `parent_version_id` must be - /// the only version that does not already have a child. - fn add_version( - &mut self, - parent_version_id: VersionId, - history_segment: HistorySegment, - ) -> Result<(AddVersionResult, SnapshotUrgency)>; - - /// Get the version with the given parent VersionId - fn get_child_version(&mut self, parent_version_id: VersionId) -> Result; - - /// Add a snapshot on the server - fn add_snapshot(&mut self, version_id: VersionId, snapshot: Snapshot) -> Result<()>; - - fn get_snapshot(&mut self) -> Result>; -} diff --git a/taskchampion/taskchampion/src/storage/config.rs b/taskchampion/taskchampion/src/storage/config.rs deleted file mode 100644 index cabc6de54..000000000 --- a/taskchampion/taskchampion/src/storage/config.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::{InMemoryStorage, SqliteStorage, Storage}; -use crate::errors::Result; -use std::path::PathBuf; - -/// The configuration required for a replica's storage. -pub enum StorageConfig { - /// Store the data on disk. This is the common choice. - OnDisk { - /// Path containing the task DB. - taskdb_dir: PathBuf, - - /// Create the DB if it does not already exist - create_if_missing: bool, - }, - /// Store the data in memory. This is only useful for testing. - InMemory, -} - -impl StorageConfig { - pub fn into_storage(self) -> Result> { - Ok(match self { - StorageConfig::OnDisk { - taskdb_dir, - create_if_missing, - } => Box::new(SqliteStorage::new(taskdb_dir, create_if_missing)?), - StorageConfig::InMemory => Box::new(InMemoryStorage::new()), - }) - } -} diff --git a/taskchampion/taskchampion/src/storage/inmemory.rs b/taskchampion/taskchampion/src/storage/inmemory.rs deleted file mode 100644 index 6bf771a93..000000000 --- a/taskchampion/taskchampion/src/storage/inmemory.rs +++ /dev/null @@ -1,246 +0,0 @@ -#![allow(clippy::new_without_default)] - -use crate::errors::{Error, Result}; -use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use std::collections::hash_map::Entry; -use std::collections::HashMap; -use uuid::Uuid; - -#[derive(PartialEq, Debug, Clone)] -struct Data { - tasks: HashMap, - base_version: VersionId, - operations: Vec, - working_set: Vec>, -} - -struct Txn<'t> { - storage: &'t mut InMemoryStorage, - new_data: Option, -} - -impl<'t> Txn<'t> { - fn mut_data_ref(&mut self) -> &mut Data { - if self.new_data.is_none() { - self.new_data = Some(self.storage.data.clone()); - } - if let Some(ref mut data) = self.new_data { - data - } else { - unreachable!(); - } - } - - fn data_ref(&mut self) -> &Data { - if let Some(ref data) = self.new_data { - data - } else { - &self.storage.data - } - } -} - -impl<'t> StorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: Uuid) -> Result> { - match self.data_ref().tasks.get(&uuid) { - None => Ok(None), - Some(t) => Ok(Some(t.clone())), - } - } - - fn create_task(&mut self, uuid: Uuid) -> Result { - if let ent @ Entry::Vacant(_) = self.mut_data_ref().tasks.entry(uuid) { - ent.or_insert_with(TaskMap::new); - Ok(true) - } else { - Ok(false) - } - } - - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Result<()> { - self.mut_data_ref().tasks.insert(uuid, task); - Ok(()) - } - - fn delete_task(&mut self, uuid: Uuid) -> Result { - Ok(self.mut_data_ref().tasks.remove(&uuid).is_some()) - } - - fn all_tasks<'a>(&mut self) -> Result> { - Ok(self - .data_ref() - .tasks - .iter() - .map(|(u, t)| (*u, t.clone())) - .collect()) - } - - fn all_task_uuids<'a>(&mut self) -> Result> { - Ok(self.data_ref().tasks.keys().copied().collect()) - } - - fn base_version(&mut self) -> Result { - Ok(self.data_ref().base_version) - } - - fn set_base_version(&mut self, version: VersionId) -> Result<()> { - self.mut_data_ref().base_version = version; - Ok(()) - } - - fn operations(&mut self) -> Result> { - Ok(self.data_ref().operations.clone()) - } - - fn num_operations(&mut self) -> Result { - Ok(self.data_ref().operations.len()) - } - - fn add_operation(&mut self, op: ReplicaOp) -> Result<()> { - self.mut_data_ref().operations.push(op); - Ok(()) - } - - fn set_operations(&mut self, ops: Vec) -> Result<()> { - self.mut_data_ref().operations = ops; - Ok(()) - } - - fn get_working_set(&mut self) -> Result>> { - Ok(self.data_ref().working_set.clone()) - } - - fn add_to_working_set(&mut self, uuid: Uuid) -> Result { - let working_set = &mut self.mut_data_ref().working_set; - working_set.push(Some(uuid)); - Ok(working_set.len()) - } - - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Result<()> { - let working_set = &mut self.mut_data_ref().working_set; - if index >= working_set.len() { - return Err(Error::Database(format!( - "Index {} is not in the working set", - index - ))); - } - working_set[index] = uuid; - Ok(()) - } - - fn clear_working_set(&mut self) -> Result<()> { - self.mut_data_ref().working_set = vec![None]; - Ok(()) - } - - fn commit(&mut self) -> Result<()> { - // copy the new_data back into storage to commit the transaction - if let Some(data) = self.new_data.take() { - self.storage.data = data; - } - Ok(()) - } -} - -/// InMemoryStorage is a simple in-memory task storage implementation. It is not useful for -/// production data, but is useful for testing purposes. -#[derive(PartialEq, Debug, Clone)] -pub struct InMemoryStorage { - data: Data, -} - -impl InMemoryStorage { - pub fn new() -> InMemoryStorage { - InMemoryStorage { - data: Data { - tasks: HashMap::new(), - base_version: DEFAULT_BASE_VERSION, - operations: vec![], - working_set: vec![None], - }, - } - } -} - -impl Storage for InMemoryStorage { - fn txn<'a>(&'a mut self) -> Result> { - Ok(Box::new(Txn { - storage: self, - new_data: None, - })) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - // (note: this module is heavily used in tests so most of its functionality is well-tested - // elsewhere and not tested here) - - #[test] - fn get_working_set_empty() -> Result<()> { - let mut storage = InMemoryStorage::new(); - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None]); - } - - Ok(()) - } - - #[test] - fn add_to_working_set() -> Result<()> { - let mut storage = InMemoryStorage::new(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); - } - - Ok(()) - } - - #[test] - fn clear_working_set() -> Result<()> { - let mut storage = InMemoryStorage::new(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.clear_working_set()?; - txn.add_to_working_set(uuid2)?; - txn.add_to_working_set(uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); - } - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/storage/mod.rs b/taskchampion/taskchampion/src/storage/mod.rs deleted file mode 100644 index 5674e1baf..000000000 --- a/taskchampion/taskchampion/src/storage/mod.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::errors::Result; -/** -This module defines the backend storage used by [`Replica`](crate::Replica). -It defines a [trait](crate::storage::Storage) for storage implementations, and provides a default on-disk implementation as well as an in-memory implementation for testing. - -Typical uses of this crate do not interact directly with this module; [`StorageConfig`](crate::StorageConfig) is sufficient. -However, users who wish to implement their own storage backends can implement the traits defined here and pass the result to [`Replica`](crate::Replica). -*/ -use std::collections::HashMap; -use uuid::Uuid; - -mod config; -mod inmemory; -mod op; -pub(crate) mod sqlite; - -pub use config::StorageConfig; -pub use inmemory::InMemoryStorage; -pub use sqlite::SqliteStorage; - -pub use op::ReplicaOp; - -/// An in-memory representation of a task as a simple hashmap -pub type TaskMap = HashMap; - -#[cfg(test)] -fn taskmap_with(mut properties: Vec<(String, String)>) -> TaskMap { - let mut rv = TaskMap::new(); - for (p, v) in properties.drain(..) { - rv.insert(p, v); - } - rv -} - -/// The type of VersionIds -pub use crate::server::VersionId; - -/// The default for base_version. -pub(crate) const DEFAULT_BASE_VERSION: Uuid = crate::server::NIL_VERSION_ID; - -/// A Storage transaction, in which storage operations are performed. -/// -/// # Concurrency -/// -/// Serializable consistency must be maintained. Concurrent access is unusual -/// and some implementations may simply apply a mutex to limit access to -/// one transaction at a time. -/// -/// # Commiting and Aborting -/// -/// A transaction is not visible to other readers until it is committed with -/// [`crate::storage::StorageTxn::commit`]. Transactions are aborted if they are dropped. -/// It is safe and performant to drop transactions that did not modify any data without committing. -pub trait StorageTxn { - /// Get an (immutable) task, if it is in the storage - fn get_task(&mut self, uuid: Uuid) -> Result>; - - /// Create an (empty) task, only if it does not already exist. Returns true if - /// the task was created (did not already exist). - fn create_task(&mut self, uuid: Uuid) -> Result; - - /// Set a task, overwriting any existing task. If the task does not exist, this implicitly - /// creates it (use `get_task` to check first, if necessary). - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Result<()>; - - /// Delete a task, if it exists. Returns true if the task was deleted (already existed) - fn delete_task(&mut self, uuid: Uuid) -> Result; - - /// Get the uuids and bodies of all tasks in the storage, in undefined order. - fn all_tasks(&mut self) -> Result>; - - /// Get the uuids of all tasks in the storage, in undefined order. - fn all_task_uuids(&mut self) -> Result>; - - /// Get the current base_version for this storage -- the last version synced from the server. - fn base_version(&mut self) -> Result; - - /// Set the current base_version for this storage. - fn set_base_version(&mut self, version: VersionId) -> Result<()>; - - /// Get the current set of outstanding operations (operations that have not been sync'd to the - /// server yet) - fn operations(&mut self) -> Result>; - - /// Get the current set of outstanding operations (operations that have not been sync'd to the - /// server yet) - fn num_operations(&mut self) -> Result; - - /// Add an operation to the end of the list of operations in the storage. Note that this - /// merely *stores* the operation; it is up to the TaskDb to apply it. - fn add_operation(&mut self, op: ReplicaOp) -> Result<()>; - - /// Replace the current list of operations with a new list. - fn set_operations(&mut self, ops: Vec) -> Result<()>; - - /// Get the entire working set, with each task UUID at its appropriate (1-based) index. - /// Element 0 is always None. - fn get_working_set(&mut self) -> Result>>; - - /// Add a task to the working set and return its (one-based) index. This index will be one greater - /// than the highest used index. - fn add_to_working_set(&mut self, uuid: Uuid) -> Result; - - /// Update the working set task at the given index. This cannot add a new item to the - /// working set. - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Result<()>; - - /// Clear all tasks from the working set in preparation for a garbage-collection operation. - /// Note that this is the only way items are removed from the set. - fn clear_working_set(&mut self) -> Result<()>; - - /// Check whether this storage is entirely empty - #[allow(clippy::wrong_self_convention)] // mut is required here for storage access - fn is_empty(&mut self) -> Result { - let mut empty = true; - empty = empty && self.all_tasks()?.is_empty(); - empty = empty && self.get_working_set()? == vec![None]; - empty = empty && self.base_version()? == Uuid::nil(); - empty = empty && self.operations()?.is_empty(); - Ok(empty) - } - - /// Commit any changes made in the transaction. It is an error to call this more than - /// once. - fn commit(&mut self) -> Result<()>; -} - -/// A trait for objects able to act as task storage. Most of the interesting behavior is in the -/// [`crate::storage::StorageTxn`] trait. -pub trait Storage { - /// Begin a transaction - fn txn<'a>(&'a mut self) -> Result>; -} diff --git a/taskchampion/taskchampion/src/storage/op.rs b/taskchampion/taskchampion/src/storage/op.rs deleted file mode 100644 index c63e9efa6..000000000 --- a/taskchampion/taskchampion/src/storage/op.rs +++ /dev/null @@ -1,289 +0,0 @@ -use crate::server::SyncOp; -use crate::storage::TaskMap; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -/// A ReplicaOp defines a single change to the task database, as stored locally in the replica. -/// This contains additional information not included in SyncOp. -#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)] -pub enum ReplicaOp { - /// Create a new task. - /// - /// On undo, the task is deleted. - Create { uuid: Uuid }, - - /// Delete an existing task. - /// - /// On undo, the task's data is restored from old_task. - Delete { uuid: Uuid, old_task: TaskMap }, - - /// Update an existing task, setting the given property to the given value. If the value is - /// None, then the corresponding property is deleted. - /// - /// On undo, the property is set back to its previous value. - Update { - uuid: Uuid, - property: String, - old_value: Option, - value: Option, - timestamp: DateTime, - }, - - /// Mark a point in the operations history to which the user might like to undo. Users - /// typically want to undo more than one operation at a time (for example, most changes update - /// both the `modified` property and some other task property -- the user would like to "undo" - /// both updates at the same time). Applying an UndoPoint does nothing. - UndoPoint, -} - -impl ReplicaOp { - /// Convert this operation into a [`SyncOp`]. - pub fn into_sync(self) -> Option { - match self { - Self::Create { uuid } => Some(SyncOp::Create { uuid }), - Self::Delete { uuid, .. } => Some(SyncOp::Delete { uuid }), - Self::Update { - uuid, - property, - value, - timestamp, - .. - } => Some(SyncOp::Update { - uuid, - property, - value, - timestamp, - }), - Self::UndoPoint => None, - } - } - - /// Determine whether this is an undo point. - pub fn is_undo_point(&self) -> bool { - self == &Self::UndoPoint - } - - /// Generate a sequence of SyncOp's to reverse the effects of this ReplicaOp. - pub fn reverse_ops(self) -> Vec { - match self { - Self::Create { uuid } => vec![SyncOp::Delete { uuid }], - Self::Delete { uuid, mut old_task } => { - let mut ops = vec![SyncOp::Create { uuid }]; - // We don't have the original update timestamp, but it doesn't - // matter because this SyncOp will just be applied and discarded. - let timestamp = Utc::now(); - for (property, value) in old_task.drain() { - ops.push(SyncOp::Update { - uuid, - property, - value: Some(value), - timestamp, - }); - } - ops - } - Self::Update { - uuid, - property, - old_value, - timestamp, - .. - } => vec![SyncOp::Update { - uuid, - property, - value: old_value, - timestamp, - }], - Self::UndoPoint => vec![], - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::errors::Result; - use crate::storage::taskmap_with; - use chrono::Utc; - use pretty_assertions::assert_eq; - - use ReplicaOp::*; - - #[test] - fn test_json_create() -> Result<()> { - let uuid = Uuid::new_v4(); - let op = Create { uuid }; - let json = serde_json::to_string(&op)?; - assert_eq!(json, format!(r#"{{"Create":{{"uuid":"{}"}}}}"#, uuid)); - let deser: ReplicaOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_delete() -> Result<()> { - let uuid = Uuid::new_v4(); - let old_task = vec![("foo".into(), "bar".into())].drain(..).collect(); - let op = Delete { uuid, old_task }; - let json = serde_json::to_string(&op)?; - assert_eq!( - json, - format!( - r#"{{"Delete":{{"uuid":"{}","old_task":{{"foo":"bar"}}}}}}"#, - uuid - ) - ); - let deser: ReplicaOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_update() -> Result<()> { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - let op = Update { - uuid, - property: "abc".into(), - old_value: Some("true".into()), - value: Some("false".into()), - timestamp, - }; - - let json = serde_json::to_string(&op)?; - assert_eq!( - json, - format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","old_value":"true","value":"false","timestamp":"{:?}"}}}}"#, - uuid, timestamp, - ) - ); - let deser: ReplicaOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_json_update_none() -> Result<()> { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - - let op = Update { - uuid, - property: "abc".into(), - old_value: None, - value: None, - timestamp, - }; - - let json = serde_json::to_string(&op)?; - assert_eq!( - json, - format!( - r#"{{"Update":{{"uuid":"{}","property":"abc","old_value":null,"value":null,"timestamp":"{:?}"}}}}"#, - uuid, timestamp, - ) - ); - let deser: ReplicaOp = serde_json::from_str(&json)?; - assert_eq!(deser, op); - Ok(()) - } - - #[test] - fn test_into_sync_create() { - let uuid = Uuid::new_v4(); - assert_eq!(Create { uuid }.into_sync(), Some(SyncOp::Create { uuid })); - } - - #[test] - fn test_into_sync_delete() { - let uuid = Uuid::new_v4(); - assert_eq!( - Delete { - uuid, - old_task: TaskMap::new() - } - .into_sync(), - Some(SyncOp::Delete { uuid }) - ); - } - - #[test] - fn test_into_sync_update() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - assert_eq!( - Update { - uuid, - property: "prop".into(), - old_value: Some("foo".into()), - value: Some("v".into()), - timestamp, - } - .into_sync(), - Some(SyncOp::Update { - uuid, - property: "prop".into(), - value: Some("v".into()), - timestamp, - }) - ); - } - - #[test] - fn test_into_sync_undo_point() { - assert_eq!(UndoPoint.into_sync(), None); - } - - #[test] - fn test_reverse_create() { - let uuid = Uuid::new_v4(); - assert_eq!(Create { uuid }.reverse_ops(), vec![SyncOp::Delete { uuid }]); - } - - #[test] - fn test_reverse_delete() { - let uuid = Uuid::new_v4(); - let reversed = Delete { - uuid, - old_task: taskmap_with(vec![("prop1".into(), "v1".into())]), - } - .reverse_ops(); - assert_eq!(reversed.len(), 2); - assert_eq!(reversed[0], SyncOp::Create { uuid }); - assert!(matches!( - &reversed[1], - SyncOp::Update { uuid: u, property: p, value: Some(v), ..} - if u == &uuid && p == "prop1" && v == "v1" - )); - } - - #[test] - fn test_reverse_update() { - let uuid = Uuid::new_v4(); - let timestamp = Utc::now(); - assert_eq!( - Update { - uuid, - property: "prop".into(), - old_value: Some("foo".into()), - value: Some("v".into()), - timestamp, - } - .reverse_ops(), - vec![SyncOp::Update { - uuid, - property: "prop".into(), - value: Some("foo".into()), - timestamp, - }] - ); - } - - #[test] - fn test_reverse_undo_point() { - assert_eq!(UndoPoint.reverse_ops(), vec![]); - } -} diff --git a/taskchampion/taskchampion/src/storage/sqlite.rs b/taskchampion/taskchampion/src/storage/sqlite.rs deleted file mode 100644 index 8abdde476..000000000 --- a/taskchampion/taskchampion/src/storage/sqlite.rs +++ /dev/null @@ -1,819 +0,0 @@ -use crate::errors::Result; -use crate::storage::{ReplicaOp, Storage, StorageTxn, TaskMap, VersionId, DEFAULT_BASE_VERSION}; -use anyhow::Context; -use rusqlite::types::{FromSql, ToSql}; -use rusqlite::{params, Connection, OpenFlags, OptionalExtension}; -use std::path::Path; -use uuid::Uuid; - -#[derive(Debug, thiserror::Error)] -pub enum SqliteError { - #[error("SQLite transaction already committted")] - TransactionAlreadyCommitted, -} - -/// Newtype to allow implementing `FromSql` for foreign `uuid::Uuid` -pub(crate) struct StoredUuid(pub(crate) Uuid); - -/// Conversion from Uuid stored as a string (rusqlite's uuid feature stores as binary blob) -impl FromSql for StoredUuid { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let u = Uuid::parse_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(StoredUuid(u)) - } -} - -/// Store Uuid as string in database -impl ToSql for StoredUuid { - fn to_sql(&self) -> rusqlite::Result> { - let s = self.0.to_string(); - Ok(s.into()) - } -} - -/// Wraps [`TaskMap`] (type alias for HashMap) so we can implement rusqlite conversion traits for it -struct StoredTaskMap(TaskMap); - -/// Parses TaskMap stored as JSON in string column -impl FromSql for StoredTaskMap { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let o: TaskMap = serde_json::from_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(StoredTaskMap(o)) - } -} - -/// Stores TaskMap in string column -impl ToSql for StoredTaskMap { - fn to_sql(&self) -> rusqlite::Result> { - let s = serde_json::to_string(&self.0) - .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; - Ok(s.into()) - } -} - -/// Stores [`ReplicaOp`] in SQLite -impl FromSql for ReplicaOp { - fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult { - let o: ReplicaOp = serde_json::from_str(value.as_str()?) - .map_err(|_| rusqlite::types::FromSqlError::InvalidType)?; - Ok(o) - } -} - -/// Parses ReplicaOp stored as JSON in string column -impl ToSql for ReplicaOp { - fn to_sql(&self) -> rusqlite::Result> { - let s = serde_json::to_string(&self) - .map_err(|e| rusqlite::Error::ToSqlConversionFailure(Box::new(e)))?; - Ok(s.into()) - } -} - -/// SqliteStorage is an on-disk storage backed by SQLite3. -pub struct SqliteStorage { - con: Connection, -} - -impl SqliteStorage { - pub fn new>(directory: P, create_if_missing: bool) -> Result { - if create_if_missing { - // Ensure parent folder exists - std::fs::create_dir_all(&directory)?; - } - - // Open (or create) database - let db_file = directory.as_ref().join("taskchampion.sqlite3"); - let mut flags = OpenFlags::default(); - // default contains SQLITE_OPEN_CREATE, so remove it if we are not to - // create a DB when missing. - if !create_if_missing { - flags.remove(OpenFlags::SQLITE_OPEN_CREATE); - } - let con = Connection::open_with_flags(db_file, flags)?; - - // Initialize database - let queries = vec![ - "CREATE TABLE IF NOT EXISTS operations (id INTEGER PRIMARY KEY AUTOINCREMENT, data STRING);", - "CREATE TABLE IF NOT EXISTS sync_meta (key STRING PRIMARY KEY, value STRING);", - "CREATE TABLE IF NOT EXISTS tasks (uuid STRING PRIMARY KEY, data STRING);", - "CREATE TABLE IF NOT EXISTS working_set (id INTEGER PRIMARY KEY, uuid STRING);", - ]; - for q in queries { - con.execute(q, []).context("Creating table")?; - } - - Ok(SqliteStorage { con }) - } -} - -struct Txn<'t> { - txn: Option>, -} - -impl<'t> Txn<'t> { - fn get_txn(&self) -> std::result::Result<&rusqlite::Transaction<'t>, SqliteError> { - self.txn - .as_ref() - .ok_or(SqliteError::TransactionAlreadyCommitted) - } - - fn get_next_working_set_number(&self) -> Result { - let t = self.get_txn()?; - let next_id: Option = t - .query_row( - "SELECT COALESCE(MAX(id), 0) + 1 FROM working_set", - [], - |r| r.get(0), - ) - .optional() - .context("Getting highest working set ID")?; - - Ok(next_id.unwrap_or(0)) - } -} - -impl Storage for SqliteStorage { - fn txn<'a>(&'a mut self) -> Result> { - let txn = self.con.transaction()?; - Ok(Box::new(Txn { txn: Some(txn) })) - } -} - -impl<'t> StorageTxn for Txn<'t> { - fn get_task(&mut self, uuid: Uuid) -> Result> { - let t = self.get_txn()?; - let result: Option = t - .query_row( - "SELECT data FROM tasks WHERE uuid = ? LIMIT 1", - [&StoredUuid(uuid)], - |r| r.get("data"), - ) - .optional()?; - - // Get task from "stored" wrapper - Ok(result.map(|t| t.0)) - } - - fn create_task(&mut self, uuid: Uuid) -> Result { - let t = self.get_txn()?; - let count: usize = t.query_row( - "SELECT count(uuid) FROM tasks WHERE uuid = ?", - [&StoredUuid(uuid)], - |x| x.get(0), - )?; - if count > 0 { - return Ok(false); - } - - let data = TaskMap::default(); - t.execute( - "INSERT INTO tasks (uuid, data) VALUES (?, ?)", - params![&StoredUuid(uuid), &StoredTaskMap(data)], - ) - .context("Create task query")?; - Ok(true) - } - - fn set_task(&mut self, uuid: Uuid, task: TaskMap) -> Result<()> { - let t = self.get_txn()?; - t.execute( - "INSERT OR REPLACE INTO tasks (uuid, data) VALUES (?, ?)", - params![&StoredUuid(uuid), &StoredTaskMap(task)], - ) - .context("Update task query")?; - Ok(()) - } - - fn delete_task(&mut self, uuid: Uuid) -> Result { - let t = self.get_txn()?; - let changed = t - .execute("DELETE FROM tasks WHERE uuid = ?", [&StoredUuid(uuid)]) - .context("Delete task query")?; - Ok(changed > 0) - } - - fn all_tasks(&mut self) -> Result> { - let t = self.get_txn()?; - - let mut q = t.prepare("SELECT uuid, data FROM tasks")?; - let rows = q.query_map([], |r| { - let uuid: StoredUuid = r.get("uuid")?; - let data: StoredTaskMap = r.get("data")?; - Ok((uuid.0, data.0)) - })?; - - let mut ret = vec![]; - for r in rows { - ret.push(r?); - } - Ok(ret) - } - - fn all_task_uuids(&mut self) -> Result> { - let t = self.get_txn()?; - - let mut q = t.prepare("SELECT uuid FROM tasks")?; - let rows = q.query_map([], |r| { - let uuid: StoredUuid = r.get("uuid")?; - Ok(uuid.0) - })?; - - let mut ret = vec![]; - for r in rows { - ret.push(r?); - } - Ok(ret) - } - - fn base_version(&mut self) -> Result { - let t = self.get_txn()?; - - let version: Option = t - .query_row( - "SELECT value FROM sync_meta WHERE key = 'base_version'", - [], - |r| r.get("value"), - ) - .optional()?; - Ok(version.map(|u| u.0).unwrap_or(DEFAULT_BASE_VERSION)) - } - - fn set_base_version(&mut self, version: VersionId) -> Result<()> { - let t = self.get_txn()?; - t.execute( - "INSERT OR REPLACE INTO sync_meta (key, value) VALUES (?, ?)", - params!["base_version", &StoredUuid(version)], - ) - .context("Set base version")?; - Ok(()) - } - - fn operations(&mut self) -> Result> { - let t = self.get_txn()?; - - let mut q = t.prepare("SELECT data FROM operations ORDER BY id ASC")?; - let rows = q.query_map([], |r| { - let data: ReplicaOp = r.get("data")?; - Ok(data) - })?; - - let mut ret = vec![]; - for r in rows { - ret.push(r?); - } - Ok(ret) - } - - fn num_operations(&mut self) -> Result { - let t = self.get_txn()?; - let count: usize = t.query_row("SELECT count(*) FROM operations", [], |x| x.get(0))?; - Ok(count) - } - - fn add_operation(&mut self, op: ReplicaOp) -> Result<()> { - let t = self.get_txn()?; - - t.execute("INSERT INTO operations (data) VALUES (?)", params![&op]) - .context("Add operation query")?; - Ok(()) - } - - fn set_operations(&mut self, ops: Vec) -> Result<()> { - let t = self.get_txn()?; - t.execute("DELETE FROM operations", []) - .context("Clear all existing operations")?; - t.execute("DELETE FROM sqlite_sequence WHERE name = 'operations'", []) - .context("Clear all existing operations")?; - - for o in ops { - self.add_operation(o)?; - } - Ok(()) - } - - fn get_working_set(&mut self) -> Result>> { - let t = self.get_txn()?; - - let mut q = t.prepare("SELECT id, uuid FROM working_set ORDER BY id ASC")?; - let rows = q - .query_map([], |r| { - let id: usize = r.get("id")?; - let uuid: StoredUuid = r.get("uuid")?; - Ok((id, uuid.0)) - }) - .context("Get working set query")?; - - let rows: Vec> = rows.collect(); - let mut res = Vec::with_capacity(rows.len()); - for _ in 0..self - .get_next_working_set_number() - .context("Getting working set number")? - { - res.push(None); - } - for r in rows { - let (id, uuid) = r?; - res[id] = Some(uuid); - } - - Ok(res) - } - - fn add_to_working_set(&mut self, uuid: Uuid) -> Result { - let t = self.get_txn()?; - - let next_working_id = self.get_next_working_set_number()?; - - t.execute( - "INSERT INTO working_set (id, uuid) VALUES (?, ?)", - params![next_working_id, &StoredUuid(uuid)], - ) - .context("Create task query")?; - - Ok(next_working_id) - } - - fn set_working_set_item(&mut self, index: usize, uuid: Option) -> Result<()> { - let t = self.get_txn()?; - match uuid { - // Add or override item - Some(uuid) => t.execute( - "INSERT OR REPLACE INTO working_set (id, uuid) VALUES (?, ?)", - params![index, &StoredUuid(uuid)], - ), - // Setting to None removes the row from database - None => t.execute("DELETE FROM working_set WHERE id = ?", [index]), - } - .context("Set working set item query")?; - Ok(()) - } - - fn clear_working_set(&mut self) -> Result<()> { - let t = self.get_txn()?; - t.execute("DELETE FROM working_set", []) - .context("Clear working set query")?; - Ok(()) - } - - fn commit(&mut self) -> Result<()> { - let t = self - .txn - .take() - .ok_or(SqliteError::TransactionAlreadyCommitted)?; - t.commit().context("Committing transaction")?; - Ok(()) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::storage::taskmap_with; - use pretty_assertions::assert_eq; - use tempfile::TempDir; - - #[test] - fn test_empty_dir() -> Result<()> { - let tmp_dir = TempDir::new()?; - let non_existant = tmp_dir.path().join("subdir"); - let mut storage = SqliteStorage::new(non_existant, true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!(task, Some(taskmap_with(vec![]))); - } - Ok(()) - } - - #[test] - fn drop_transaction() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid1)?); - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid2)?); - std::mem::drop(txn); // Unnecessary explicit drop of transaction - } - - { - let mut txn = storage.txn()?; - let uuids = txn.all_task_uuids()?; - - assert_eq!(uuids, [uuid1]); - } - - Ok(()) - } - - #[test] - fn test_create() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!(task, Some(taskmap_with(vec![]))); - } - Ok(()) - } - - #[test] - fn test_create_exists() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert!(!txn.create_task(uuid)?); - txn.commit()?; - } - Ok(()) - } - - #[test] - fn test_get_missing() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!(task, None); - } - Ok(()) - } - - #[test] - fn test_set_task() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - txn.set_task(uuid, taskmap_with(vec![("k".to_string(), "v".to_string())]))?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let task = txn.get_task(uuid)?; - assert_eq!( - task, - Some(taskmap_with(vec![("k".to_string(), "v".to_string())])) - ); - } - Ok(()) - } - - #[test] - fn test_delete_task_missing() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(!txn.delete_task(uuid)?); - } - Ok(()) - } - - #[test] - fn test_delete_task_exists() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid)?); - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert!(txn.delete_task(uuid)?); - } - Ok(()) - } - - #[test] - fn test_all_tasks_empty() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - { - let mut txn = storage.txn()?; - let tasks = txn.all_tasks()?; - assert_eq!(tasks, vec![]); - } - Ok(()) - } - - #[test] - fn test_all_tasks_and_uuids() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - assert!(txn.create_task(uuid1)?); - txn.set_task( - uuid1, - taskmap_with(vec![("num".to_string(), "1".to_string())]), - )?; - assert!(txn.create_task(uuid2)?); - txn.set_task( - uuid2, - taskmap_with(vec![("num".to_string(), "2".to_string())]), - )?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - let mut tasks = txn.all_tasks()?; - - // order is nondeterministic, so sort by uuid - tasks.sort_by(|a, b| a.0.cmp(&b.0)); - - let mut exp = vec![ - ( - uuid1, - taskmap_with(vec![("num".to_string(), "1".to_string())]), - ), - ( - uuid2, - taskmap_with(vec![("num".to_string(), "2".to_string())]), - ), - ]; - exp.sort_by(|a, b| a.0.cmp(&b.0)); - - assert_eq!(tasks, exp); - } - { - let mut txn = storage.txn()?; - let mut uuids = txn.all_task_uuids()?; - uuids.sort(); - - let mut exp = vec![uuid1, uuid2]; - exp.sort(); - - assert_eq!(uuids, exp); - } - Ok(()) - } - - #[test] - fn test_base_version_default() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - { - let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, DEFAULT_BASE_VERSION); - } - Ok(()) - } - - #[test] - fn test_base_version_setting() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let u = Uuid::new_v4(); - { - let mut txn = storage.txn()?; - txn.set_base_version(u)?; - txn.commit()?; - } - { - let mut txn = storage.txn()?; - assert_eq!(txn.base_version()?, u); - } - Ok(()) - } - - #[test] - fn test_operations() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let uuid3 = Uuid::new_v4(); - - // create some operations - { - let mut txn = storage.txn()?; - txn.add_operation(ReplicaOp::Create { uuid: uuid1 })?; - txn.add_operation(ReplicaOp::Create { uuid: uuid2 })?; - txn.commit()?; - } - - // read them back - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - ReplicaOp::Create { uuid: uuid1 }, - ReplicaOp::Create { uuid: uuid2 }, - ] - ); - - assert_eq!(txn.num_operations()?, 2); - } - - // set them to a different bunch - { - let mut txn = storage.txn()?; - txn.set_operations(vec![ - ReplicaOp::Delete { - uuid: uuid2, - old_task: TaskMap::new(), - }, - ReplicaOp::Delete { - uuid: uuid1, - old_task: TaskMap::new(), - }, - ])?; - txn.commit()?; - } - - // create some more operations (to test adding operations after clearing) - { - let mut txn = storage.txn()?; - txn.add_operation(ReplicaOp::Create { uuid: uuid3 })?; - txn.add_operation(ReplicaOp::Delete { - uuid: uuid3, - old_task: TaskMap::new(), - })?; - txn.commit()?; - } - - // read them back - { - let mut txn = storage.txn()?; - let ops = txn.operations()?; - assert_eq!( - ops, - vec![ - ReplicaOp::Delete { - uuid: uuid2, - old_task: TaskMap::new() - }, - ReplicaOp::Delete { - uuid: uuid1, - old_task: TaskMap::new() - }, - ReplicaOp::Create { uuid: uuid3 }, - ReplicaOp::Delete { - uuid: uuid3, - old_task: TaskMap::new() - }, - ] - ); - assert_eq!(txn.num_operations()?, 4); - } - Ok(()) - } - - #[test] - fn get_working_set_empty() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None]); - } - - Ok(()) - } - - #[test] - fn add_to_working_set() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); - } - - Ok(()) - } - - #[test] - fn clear_working_set() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - txn.clear_working_set()?; - txn.add_to_working_set(uuid2)?; - txn.add_to_working_set(uuid1)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid2), Some(uuid1)]); - } - - Ok(()) - } - - #[test] - fn set_working_set_item() -> Result<()> { - let tmp_dir = TempDir::new()?; - let mut storage = SqliteStorage::new(tmp_dir.path(), true)?; - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - { - let mut txn = storage.txn()?; - txn.add_to_working_set(uuid1)?; - txn.add_to_working_set(uuid2)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, Some(uuid1), Some(uuid2)]); - } - - // Clear one item - { - let mut txn = storage.txn()?; - txn.set_working_set_item(1, None)?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, None, Some(uuid2)]); - } - - // Override item - { - let mut txn = storage.txn()?; - txn.set_working_set_item(2, Some(uuid1))?; - txn.commit()?; - } - - { - let mut txn = storage.txn()?; - let ws = txn.get_working_set()?; - assert_eq!(ws, vec![None, None, Some(uuid1)]); - } - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/task/annotation.rs b/taskchampion/taskchampion/src/task/annotation.rs deleted file mode 100644 index 951dc3f11..000000000 --- a/taskchampion/taskchampion/src/task/annotation.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::Timestamp; - -/// An annotation for a task -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct Annotation { - /// Time the annotation was made - pub entry: Timestamp, - /// Content of the annotation - pub description: String, -} diff --git a/taskchampion/taskchampion/src/task/mod.rs b/taskchampion/taskchampion/src/task/mod.rs deleted file mode 100644 index 259ed6ab0..000000000 --- a/taskchampion/taskchampion/src/task/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![allow(clippy::module_inception)] -mod annotation; -mod status; -mod tag; -mod task; -mod time; - -pub use annotation::Annotation; -pub use status::Status; -pub use tag::Tag; -pub use task::{Task, TaskMut}; -pub use time::{utc_timestamp, Timestamp}; diff --git a/taskchampion/taskchampion/src/task/status.rs b/taskchampion/taskchampion/src/task/status.rs deleted file mode 100644 index 38d017df7..000000000 --- a/taskchampion/taskchampion/src/task/status.rs +++ /dev/null @@ -1,74 +0,0 @@ -/// The status of a task, as defined by the task data model. -#[derive(Debug, PartialEq, Eq, Clone, strum_macros::Display)] -#[repr(C)] -pub enum Status { - Pending, - Completed, - Deleted, - Recurring, - /// Unknown signifies a status in the task DB that was not - /// recognized. This supports forward-compatibility if a - /// new status is added. Tasks with unknown status should - /// be ignored (but not deleted). - Unknown(String), -} - -impl Status { - /// Get a Status from the string value in a TaskMap - pub(crate) fn from_taskmap(s: &str) -> Status { - match s { - "pending" => Status::Pending, - "completed" => Status::Completed, - "deleted" => Status::Deleted, - "recurring" => Status::Recurring, - v => Status::Unknown(v.to_string()), - } - } - - /// Get the 1-character value for this status to use in the TaskMap. - pub(crate) fn to_taskmap(&self) -> &str { - match self { - Status::Pending => "pending", - Status::Completed => "completed", - Status::Deleted => "deleted", - Status::Recurring => "recurring", - Status::Unknown(v) => v.as_ref(), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn to_taskmap() { - assert_eq!(Status::Pending.to_taskmap(), "pending"); - assert_eq!(Status::Completed.to_taskmap(), "completed"); - assert_eq!(Status::Deleted.to_taskmap(), "deleted"); - assert_eq!(Status::Recurring.to_taskmap(), "recurring"); - assert_eq!(Status::Unknown("wishful".into()).to_taskmap(), "wishful"); - } - - #[test] - fn from_taskmap() { - assert_eq!(Status::from_taskmap("pending"), Status::Pending); - assert_eq!(Status::from_taskmap("completed"), Status::Completed); - assert_eq!(Status::from_taskmap("deleted"), Status::Deleted); - assert_eq!(Status::from_taskmap("recurring"), Status::Recurring); - assert_eq!( - Status::from_taskmap("something-else"), - Status::Unknown("something-else".into()) - ); - } - - #[test] - fn display() { - assert_eq!(format!("{}", Status::Pending), "Pending"); - assert_eq!(format!("{}", Status::Completed), "Completed"); - assert_eq!(format!("{}", Status::Deleted), "Deleted"); - assert_eq!(format!("{}", Status::Recurring), "Recurring"); - assert_eq!(format!("{}", Status::Unknown("wishful".into())), "Unknown"); - } -} diff --git a/taskchampion/taskchampion/src/task/tag.rs b/taskchampion/taskchampion/src/task/tag.rs deleted file mode 100644 index a4f1e677c..000000000 --- a/taskchampion/taskchampion/src/task/tag.rs +++ /dev/null @@ -1,174 +0,0 @@ -use std::convert::TryFrom; -use std::fmt; -use std::str::FromStr; - -/// A Tag is a descriptor for a task, that is either present or absent, and can be used for -/// filtering. Tags composed of all uppercase letters are reserved for synthetic tags. -/// -/// Valid tags must not contain whitespace or any of the characters in `+-*/(<>^! %=~`. -/// The first characters additionally cannot be a digit, and subsequent characters cannot be `:`. -/// This definition is based on [that of -/// TaskWarrior](https://github.com/GothenburgBitFactory/taskwarrior/blob/663c6575ceca5bd0135ae884879339dac89d3142/src/Lexer.cpp#L146-L164). -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Tag(TagInner); - -/// Inner type to hide the implementation -#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub(super) enum TagInner { - User(String), - Synthetic(SyntheticTag), -} - -// see doc comment for Tag, above -pub const INVALID_TAG_CHARACTERS: &str = "+-*/(<>^! %=~"; - -impl Tag { - /// True if this tag is a synthetic tag - pub fn is_synthetic(&self) -> bool { - matches!(self.0, TagInner::Synthetic(_)) - } - - /// True if this tag is a user-provided tag (not synthetic) - pub fn is_user(&self) -> bool { - matches!(self.0, TagInner::User(_)) - } - - pub(super) fn inner(&self) -> &TagInner { - &self.0 - } - - pub(super) fn from_inner(inner: TagInner) -> Self { - Self(inner) - } -} - -impl FromStr for Tag { - type Err = anyhow::Error; - - fn from_str(value: &str) -> Result { - fn err(value: &str) -> Result { - anyhow::bail!("invalid tag {:?}", value) - } - - // first, look for synthetic tags - if value.chars().all(|c| c.is_ascii_uppercase()) { - if let Ok(st) = SyntheticTag::from_str(value) { - return Ok(Self(TagInner::Synthetic(st))); - } - // all uppercase, but not a valid synthetic tag - return err(value); - } - - if let Some(c) = value.chars().next() { - if c.is_whitespace() || c.is_ascii_digit() || INVALID_TAG_CHARACTERS.contains(c) { - return err(value); - } - } else { - return err(value); - } - if !value - .chars() - .skip(1) - .all(|c| !(c.is_whitespace() || c == ':' || INVALID_TAG_CHARACTERS.contains(c))) - { - return err(value); - } - Ok(Self(TagInner::User(String::from(value)))) - } -} - -impl TryFrom<&str> for Tag { - type Error = anyhow::Error; - - fn try_from(value: &str) -> Result { - Self::from_str(value) - } -} - -impl TryFrom<&String> for Tag { - type Error = anyhow::Error; - - fn try_from(value: &String) -> Result { - Self::from_str(&value[..]) - } -} - -impl fmt::Display for Tag { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - TagInner::User(s) => s.fmt(f), - TagInner::Synthetic(st) => st.as_ref().fmt(f), - } - } -} - -impl AsRef for Tag { - fn as_ref(&self) -> &str { - match &self.0 { - TagInner::User(s) => s.as_ref(), - TagInner::Synthetic(st) => st.as_ref(), - } - } -} - -/// A synthetic tag, represented as an `enum`. This type is used directly by -/// [`taskchampion::task::task`] for efficiency. -#[derive( - Debug, - Clone, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - strum_macros::EnumString, - strum_macros::AsRefStr, - strum_macros::EnumIter, -)] -#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] -pub(super) enum SyntheticTag { - // When adding items here, also implement and test them in `task.rs` and document them in - // `docs/src/tags.md`. - Waiting, - Active, - Pending, - Completed, - Deleted, - Blocked, - Unblocked, - Blocking, -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - use rstest::rstest; - use std::convert::TryInto; - - #[rstest] - #[case::simple("abc")] - #[case::colon_prefix(":abc")] - #[case::letters_and_numbers("a123_456")] - #[case::synthetic("WAITING")] - fn test_tag_try_into_success(#[case] s: &'static str) { - let tag: Tag = s.try_into().unwrap(); - // check Display (via to_string) and AsRef while we're here - assert_eq!(tag.to_string(), s.to_owned()); - assert_eq!(tag.as_ref(), s); - } - - #[rstest] - #[case::empty("")] - #[case::colon_infix("a:b")] - #[case::digits("999")] - #[case::bangs("abc!!!")] - #[case::no_such_synthetic("NOSUCH")] - fn test_tag_try_into_err(#[case] s: &'static str) { - let tag: Result = s.try_into(); - assert_eq!( - tag.unwrap_err().to_string(), - format!("invalid tag \"{}\"", s) - ); - } -} diff --git a/taskchampion/taskchampion/src/task/task.rs b/taskchampion/taskchampion/src/task/task.rs deleted file mode 100644 index 3dda37d61..000000000 --- a/taskchampion/taskchampion/src/task/task.rs +++ /dev/null @@ -1,1270 +0,0 @@ -use super::tag::{SyntheticTag, TagInner}; -use super::{utc_timestamp, Annotation, Status, Tag, Timestamp}; -use crate::depmap::DependencyMap; -use crate::errors::{Error, Result}; -use crate::replica::Replica; -use crate::storage::TaskMap; -use chrono::prelude::*; -use log::trace; -use std::convert::AsRef; -use std::convert::TryInto; -use std::rc::Rc; -use std::str::FromStr; -use uuid::Uuid; - -/* The Task and TaskMut classes wrap the underlying [`TaskMap`], which is a simple key/value map. - * They provide semantic meaning to that TaskMap according to the TaskChampion data model. For - * example, [`get_status`](Task::get_status) and [`set_status`](TaskMut::set_status) translate from - * strings in the TaskMap to [`Status`]. - * - * The same approach applies for more complex data such as dependencies or annotations. Users of - * this API should only need the [`get_taskmap`](Task::get_taskmap) method for debugging purposes, - * and should never need to make changes to the TaskMap directly. - */ - -/// A task, as publicly exposed by this crate. -/// -/// Note that Task objects represent a snapshot of the task at a moment in time, and are not -/// protected by the atomicity of the backend storage. Concurrent modifications are safe, -/// but a Task that is cached for more than a few seconds may cause the user to see stale -/// data. Fetch, use, and drop Tasks quickly. -/// -/// This struct contains only getters for various values on the task. The -/// [`into_mut`](Task::into_mut) method -/// returns a TaskMut which can be used to modify the task. -#[derive(Debug, Clone)] -pub struct Task { - uuid: Uuid, - taskmap: TaskMap, - depmap: Rc, -} - -impl PartialEq for Task { - fn eq(&self, other: &Task) -> bool { - // compare only the taskmap and uuid; depmap is just present for reference - self.uuid == other.uuid && self.taskmap == other.taskmap - } -} - -/// A mutable task, with setter methods. -/// -/// Most methods are simple setters and not further described. Calling a setter will update the -/// referenced Replica, as well as the included Task, immediately. -/// -/// The [`Task`] methods are available on [`TaskMut`] via [`Deref`](std::ops::Deref). -pub struct TaskMut<'r> { - task: Task, - replica: &'r mut Replica, - updated_modified: bool, -} - -/// An enum containing all of the key names defined in the data model, with the exception -/// of the properties containing data (`tag_..`, etc.) -#[derive(strum_macros::AsRefStr, strum_macros::EnumString)] -#[strum(serialize_all = "kebab-case")] -enum Prop { - Description, - Due, - Modified, - Start, - Status, - Priority, - Wait, - End, - Entry, -} - -#[allow(clippy::ptr_arg)] -fn uda_string_to_tuple(key: &str) -> (&str, &str) { - let mut iter = key.splitn(2, '.'); - let first = iter.next().unwrap(); - let second = iter.next(); - if let Some(second) = second { - (first, second) - } else { - ("", first) - } -} - -fn uda_tuple_to_string(namespace: impl AsRef, key: impl AsRef) -> String { - let namespace = namespace.as_ref(); - let key = key.as_ref(); - if namespace.is_empty() { - key.into() - } else { - format!("{}.{}", namespace, key) - } -} - -impl Task { - pub(crate) fn new(uuid: Uuid, taskmap: TaskMap, depmap: Rc) -> Task { - Task { - uuid, - taskmap, - depmap, - } - } - - pub fn get_uuid(&self) -> Uuid { - self.uuid - } - - pub fn get_taskmap(&self) -> &TaskMap { - &self.taskmap - } - - /// Prepare to mutate this task, requiring a mutable Replica - /// in order to update the data it contains. - pub fn into_mut(self, replica: &mut Replica) -> TaskMut { - TaskMut { - task: self, - replica, - updated_modified: false, - } - } - - pub fn get_status(&self) -> Status { - self.taskmap - .get(Prop::Status.as_ref()) - .map(|s| Status::from_taskmap(s)) - .unwrap_or(Status::Pending) - } - - pub fn get_description(&self) -> &str { - self.taskmap - .get(Prop::Description.as_ref()) - .map(|s| s.as_ref()) - .unwrap_or("") - } - - pub fn get_entry(&self) -> Option { - self.get_timestamp(Prop::Entry.as_ref()) - } - - pub fn get_priority(&self) -> &str { - self.taskmap - .get(Prop::Priority.as_ref()) - .map(|s| s.as_ref()) - .unwrap_or("") - } - - /// Get the wait time. If this value is set, it will be returned, even - /// if it is in the past. - pub fn get_wait(&self) -> Option { - self.get_timestamp(Prop::Wait.as_ref()) - } - - /// Determine whether this task is waiting now. - pub fn is_waiting(&self) -> bool { - if let Some(ts) = self.get_wait() { - return ts > Utc::now(); - } - false - } - - /// Determine whether this task is active -- that is, that it has been started - /// and not stopped. - pub fn is_active(&self) -> bool { - self.taskmap.contains_key(Prop::Start.as_ref()) - } - - /// Determine whether this task is blocked -- that is, has at least one unresolved dependency. - pub fn is_blocked(&self) -> bool { - self.depmap.dependencies(self.uuid).next().is_some() - } - - /// Determine whether this task is blocking -- that is, has at least one unresolved dependent. - pub fn is_blocking(&self) -> bool { - self.depmap.dependents(self.uuid).next().is_some() - } - - /// Determine whether a given synthetic tag is present on this task. All other - /// synthetic tag calculations are based on this one. - fn has_synthetic_tag(&self, synth: &SyntheticTag) -> bool { - match synth { - SyntheticTag::Waiting => self.is_waiting(), - SyntheticTag::Active => self.is_active(), - SyntheticTag::Pending => self.get_status() == Status::Pending, - SyntheticTag::Completed => self.get_status() == Status::Completed, - SyntheticTag::Deleted => self.get_status() == Status::Deleted, - SyntheticTag::Blocked => self.is_blocked(), - SyntheticTag::Unblocked => !self.is_blocked(), - SyntheticTag::Blocking => self.is_blocking(), - } - } - - /// Check if this task has the given tag - pub fn has_tag(&self, tag: &Tag) -> bool { - match tag.inner() { - TagInner::User(s) => self.taskmap.contains_key(&format!("tag_{}", s)), - TagInner::Synthetic(st) => self.has_synthetic_tag(st), - } - } - - /// Iterate over the task's tags - pub fn get_tags(&self) -> impl Iterator + '_ { - use strum::IntoEnumIterator; - - self.taskmap - .iter() - .filter_map(|(k, _)| { - if let Some(tag) = k.strip_prefix("tag_") { - if let Ok(tag) = tag.try_into() { - return Some(tag); - } - // note that invalid "tag_*" are ignored - } - None - }) - .chain( - SyntheticTag::iter() - .filter(move |st| self.has_synthetic_tag(st)) - .map(|st| Tag::from_inner(TagInner::Synthetic(st))), - ) - } - - /// Iterate over the task's annotations, in arbitrary order. - pub fn get_annotations(&self) -> impl Iterator + '_ { - self.taskmap.iter().filter_map(|(k, v)| { - if let Some(ts) = k.strip_prefix("annotation_") { - if let Ok(ts) = ts.parse::() { - return Some(Annotation { - entry: utc_timestamp(ts), - description: v.to_owned(), - }); - } - // note that invalid "annotation_*" are ignored - } - None - }) - } - - /// Get the named user defined attributes (UDA). This will return None - /// for any key defined in the Task data model, regardless of whether - /// it is set or not. - pub fn get_uda(&self, namespace: &str, key: &str) -> Option<&str> { - self.get_legacy_uda(uda_tuple_to_string(namespace, key).as_ref()) - } - - /// Get the user defined attributes (UDAs) of this task, in arbitrary order. Each key is split - /// on the first `.` character. Legacy keys that do not contain `.` are represented as `("", - /// key)`. - pub fn get_udas(&self) -> impl Iterator + '_ { - self.taskmap - .iter() - .filter(|(k, _)| !Task::is_known_key(k)) - .map(|(k, v)| (uda_string_to_tuple(k), v.as_ref())) - } - - /// Get the named user defined attribute (UDA) in a legacy format. This will return None for - /// any key defined in the Task data model, regardless of whether it is set or not. - pub fn get_legacy_uda(&self, key: &str) -> Option<&str> { - if Task::is_known_key(key) { - return None; - } - self.taskmap.get(key).map(|s| s.as_ref()) - } - - /// Like `get_udas`, but returning each UDA key as a single string. - pub fn get_legacy_udas(&self) -> impl Iterator + '_ { - self.taskmap - .iter() - .filter(|(p, _)| !Task::is_known_key(p)) - .map(|(p, v)| (p.as_ref(), v.as_ref())) - } - - /// Get the modification time for this task. - pub fn get_modified(&self) -> Option { - self.get_timestamp(Prop::Modified.as_ref()) - } - - /// Get the due time for this task. - pub fn get_due(&self) -> Option { - self.get_timestamp(Prop::Due.as_ref()) - } - - /// Get the UUIDs of tasks on which this task depends. - /// - /// This includes all dependencies, regardless of their status. In fact, it may include - /// dependencies that do not exist. - pub fn get_dependencies(&self) -> impl Iterator + '_ { - self.taskmap.iter().filter_map(|(p, _)| { - if let Some(dep_str) = p.strip_prefix("dep_") { - if let Ok(u) = Uuid::parse_str(dep_str) { - return Some(u); - } - // (un-parseable dep_.. properties are ignored) - } - None - }) - } - - /// Get task's property value by name. - pub fn get_value>(&self, property: S) -> Option<&str> { - let property = property.into(); - self.taskmap.get(&property).map(|s| s.as_ref()) - } - - // -- utility functions - - fn is_known_key(key: &str) -> bool { - Prop::from_str(key).is_ok() - || key.starts_with("tag_") - || key.starts_with("annotation_") - || key.starts_with("dep_") - } - - fn get_timestamp(&self, property: &str) -> Option { - if let Some(ts) = self.taskmap.get(property) { - if let Ok(ts) = ts.parse() { - return Some(utc_timestamp(ts)); - } - // if the value does not parse as an integer, default to None - } - None - } -} - -impl<'r> TaskMut<'r> { - /// Get the immutable version of this object, ending the exclusive reference to the Replica. - pub fn into_immut(self) -> Task { - self.task - } - - /// Set the task's status. This also adds the task to the working set if the - /// new status puts it in that set. - pub fn set_status(&mut self, status: Status) -> Result<()> { - match status { - Status::Pending | Status::Recurring => { - // clear "end" when a task becomes "pending" or "recurring" - if self.taskmap.contains_key(Prop::End.as_ref()) { - self.set_timestamp(Prop::End.as_ref(), None)?; - } - // ..and add to working set - self.replica.add_to_working_set(self.uuid)?; - } - Status::Completed | Status::Deleted => { - // set "end" when a task is deleted or completed - if !self.taskmap.contains_key(Prop::End.as_ref()) { - self.set_timestamp(Prop::End.as_ref(), Some(Utc::now()))?; - } - } - _ => {} - } - self.set_string( - Prop::Status.as_ref(), - Some(String::from(status.to_taskmap())), - ) - } - - pub fn set_description(&mut self, description: String) -> Result<()> { - self.set_string(Prop::Description.as_ref(), Some(description)) - } - - pub fn set_priority(&mut self, priority: String) -> Result<()> { - self.set_string(Prop::Priority.as_ref(), Some(priority)) - } - - pub fn set_entry(&mut self, entry: Option) -> Result<()> { - self.set_timestamp(Prop::Entry.as_ref(), entry) - } - - pub fn set_wait(&mut self, wait: Option) -> Result<()> { - self.set_timestamp(Prop::Wait.as_ref(), wait) - } - - pub fn set_modified(&mut self, modified: Timestamp) -> Result<()> { - self.set_timestamp(Prop::Modified.as_ref(), Some(modified)) - } - - /// Set a tasks's property by name. - /// - /// This will not automatically update the `modified` timestamp or perform any other - /// "automatic" operations -- it simply sets the property. Howerver, if property is - /// "modified", then subsequent calls to other `set_..` methods will not update the - /// `modified` timestamp. - pub fn set_value>(&mut self, property: S, value: Option) -> Result<()> { - let property = property.into(); - - if &property == "modified" { - self.updated_modified = true; - } - - if let Some(ref v) = value { - trace!("task {}: set property {}={:?}", self.task.uuid, property, v); - } else { - trace!("task {}: remove property {}", self.task.uuid, property); - } - - self.task.taskmap = self - .replica - .update_task(self.task.uuid, &property, value.as_ref())?; - - Ok(()) - } - - /// Start the task by creating "start": "", if the task is not already - /// active. - pub fn start(&mut self) -> Result<()> { - if self.is_active() { - return Ok(()); - } - self.set_timestamp(Prop::Start.as_ref(), Some(Utc::now())) - } - - /// Stop the task by removing the `start` key - pub fn stop(&mut self) -> Result<()> { - self.set_timestamp(Prop::Start.as_ref(), None) - } - - /// Mark this task as complete - pub fn done(&mut self) -> Result<()> { - self.set_status(Status::Completed) - } - - /// Mark this task as deleted. - /// - /// Note that this does not delete the task. It merely marks the task as - /// deleted. - pub fn delete(&mut self) -> Result<()> { - self.set_status(Status::Deleted) - } - - /// Add a tag to this task. Does nothing if the tag is already present. - pub fn add_tag(&mut self, tag: &Tag) -> Result<()> { - if tag.is_synthetic() { - return Err(Error::Usage(String::from( - "Synthetic tags cannot be modified", - ))); - } - self.set_string(format!("tag_{}", tag), Some("".to_owned())) - } - - /// Remove a tag from this task. Does nothing if the tag is not present. - pub fn remove_tag(&mut self, tag: &Tag) -> Result<()> { - if tag.is_synthetic() { - return Err(Error::Usage(String::from( - "Synthetic tags cannot be modified", - ))); - } - self.set_string(format!("tag_{}", tag), None) - } - - /// Add a new annotation. Note that annotations with the same entry time - /// will overwrite one another. - pub fn add_annotation(&mut self, ann: Annotation) -> Result<()> { - self.set_string( - format!("annotation_{}", ann.entry.timestamp()), - Some(ann.description), - ) - } - - /// Remove an annotation, based on its entry time. - pub fn remove_annotation(&mut self, entry: Timestamp) -> Result<()> { - self.set_string(format!("annotation_{}", entry.timestamp()), None) - } - - pub fn set_due(&mut self, due: Option) -> Result<()> { - self.set_timestamp(Prop::Due.as_ref(), due) - } - - /// Set a user-defined attribute (UDA). This will fail if the key is defined by the data - /// model. - pub fn set_uda( - &mut self, - namespace: impl AsRef, - key: impl AsRef, - value: impl Into, - ) -> Result<()> { - let key = uda_tuple_to_string(namespace, key); - self.set_legacy_uda(key, value) - } - - /// Remove a user-defined attribute (UDA). This will fail if the key is defined by the data - /// model. - pub fn remove_uda(&mut self, namespace: impl AsRef, key: impl AsRef) -> Result<()> { - let key = uda_tuple_to_string(namespace, key); - self.remove_legacy_uda(key) - } - - /// Set a user-defined attribute (UDA), where the key is a legacy key. - pub fn set_legacy_uda( - &mut self, - key: impl Into, - value: impl Into, - ) -> Result<()> { - let key = key.into(); - if Task::is_known_key(&key) { - return Err(Error::Usage(format!( - "Property name {} as special meaning in a task and cannot be used as a UDA", - key - ))); - } - self.set_string(key, Some(value.into())) - } - - /// Remove a user-defined attribute (UDA), where the key is a legacy key. - pub fn remove_legacy_uda(&mut self, key: impl Into) -> Result<()> { - let key = key.into(); - if Task::is_known_key(&key) { - return Err(Error::Usage(format!( - "Property name {} as special meaning in a task and cannot be used as a UDA", - key - ))); - } - self.set_string(key, None) - } - - /// Add a dependency. - pub fn add_dependency(&mut self, dep: Uuid) -> Result<()> { - let key = format!("dep_{}", dep); - self.set_string(key, Some("".to_string())) - } - - /// Remove a dependency. - pub fn remove_dependency(&mut self, dep: Uuid) -> Result<()> { - let key = format!("dep_{}", dep); - self.set_string(key, None) - } - - // -- utility functions - - fn update_modified(&mut self) -> Result<()> { - if !self.updated_modified { - let now = format!("{}", Utc::now().timestamp()); - trace!("task {}: set property modified={:?}", self.task.uuid, now); - self.task.taskmap = - self.replica - .update_task(self.task.uuid, Prop::Modified.as_ref(), Some(now))?; - self.updated_modified = true; - } - Ok(()) - } - - fn set_string>(&mut self, property: S, value: Option) -> Result<()> { - let property = property.into(); - // update the modified timestamp unless we are setting it explicitly - if &property != "modified" { - self.update_modified()?; - } - - self.set_value(property, value) - } - - fn set_timestamp(&mut self, property: &str, value: Option) -> Result<()> { - self.set_string(property, value.map(|v| v.timestamp().to_string())) - } - - /// Used by tests to ensure that updates are properly written - #[cfg(test)] - fn reload(&mut self) -> Result<()> { - let uuid = self.uuid; - let task = self.replica.get_task(uuid)?.unwrap(); - self.task.taskmap = task.taskmap; - Ok(()) - } -} - -impl<'r> std::ops::Deref for TaskMut<'r> { - type Target = Task; - - fn deref(&self) -> &Self::Target { - &self.task - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - use std::collections::HashSet; - - fn dm() -> Rc { - Rc::new(DependencyMap::new()) - } - - fn with_mut_task(f: F) { - let mut replica = Replica::new_inmemory(); - let task = replica.new_task(Status::Pending, "test".into()).unwrap(); - let task = task.into_mut(&mut replica); - f(task) - } - - /// Create a user tag, without checking its validity - fn utag(name: &'static str) -> Tag { - Tag::from_inner(TagInner::User(name.into())) - } - - /// Create a synthetic tag - fn stag(synth: SyntheticTag) -> Tag { - Tag::from_inner(TagInner::Synthetic(synth)) - } - - #[test] - fn test_is_active_never_started() { - let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); - assert!(!task.is_active()); - } - - #[test] - fn test_is_active_active() { - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("start"), String::from("1234"))] - .drain(..) - .collect(), - dm(), - ); - - assert!(task.is_active()); - } - - #[test] - fn test_is_active_inactive() { - let task = Task::new(Uuid::new_v4(), Default::default(), dm()); - assert!(!task.is_active()); - } - - #[test] - fn test_entry_not_set() { - let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); - assert_eq!(task.get_entry(), None); - } - - #[test] - fn test_entry_set() { - let ts = Utc.ymd(1980, 1, 1).and_hms(0, 0, 0); - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("entry"), format!("{}", ts.timestamp()))] - .drain(..) - .collect(), - dm(), - ); - assert_eq!(task.get_entry(), Some(ts)); - } - - #[test] - fn test_wait_not_set() { - let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); - - assert!(!task.is_waiting()); - assert_eq!(task.get_wait(), None); - } - - #[test] - fn test_wait_in_past() { - let ts = Utc.ymd(1970, 1, 1).and_hms(0, 0, 0); - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("wait"), format!("{}", ts.timestamp()))] - .drain(..) - .collect(), - dm(), - ); - - assert!(!task.is_waiting()); - assert_eq!(task.get_wait(), Some(ts)); - } - - #[test] - fn test_wait_in_future() { - let ts = Utc.ymd(3000, 1, 1).and_hms(0, 0, 0); - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("wait"), format!("{}", ts.timestamp()))] - .drain(..) - .collect(), - dm(), - ); - - assert!(task.is_waiting()); - assert_eq!(task.get_wait(), Some(ts)); - } - - #[test] - fn test_has_tag() { - let task = Task::new( - Uuid::new_v4(), - vec![ - (String::from("tag_abc"), String::from("")), - (String::from("start"), String::from("1234")), - ] - .drain(..) - .collect(), - dm(), - ); - - assert!(task.has_tag(&utag("abc"))); - assert!(!task.has_tag(&utag("def"))); - assert!(task.has_tag(&stag(SyntheticTag::Active))); - assert!(task.has_tag(&stag(SyntheticTag::Pending))); - assert!(!task.has_tag(&stag(SyntheticTag::Waiting))); - } - - #[test] - fn test_get_tags() { - let task = Task::new( - Uuid::new_v4(), - vec![ - (String::from("tag_abc"), String::from("")), - (String::from("tag_def"), String::from("")), - // set `wait` so the synthetic tag WAITING is present - (String::from("wait"), String::from("33158909732")), - ] - .drain(..) - .collect(), - dm(), - ); - - let tags: HashSet<_> = task.get_tags().collect(); - let exp = set![ - utag("abc"), - utag("def"), - stag(SyntheticTag::Pending), - stag(SyntheticTag::Waiting), - stag(SyntheticTag::Unblocked), - ]; - assert_eq!(tags, exp); - } - - #[test] - fn test_get_tags_invalid_tags() { - let task = Task::new( - Uuid::new_v4(), - vec![ - (String::from("tag_ok"), String::from("")), - (String::from("tag_"), String::from("")), - (String::from("tag_123"), String::from("")), - (String::from("tag_a!!"), String::from("")), - ] - .drain(..) - .collect(), - dm(), - ); - - // only "ok" is OK - let tags: HashSet<_> = task.get_tags().collect(); - assert_eq!( - tags, - set![ - utag("ok"), - stag(SyntheticTag::Pending), - stag(SyntheticTag::Unblocked) - ] - ); - } - - #[test] - fn test_get_due() { - let test_time = Utc.ymd(2033, 1, 1).and_hms(0, 0, 0); - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("due"), format!("{}", test_time.timestamp()))] - .drain(..) - .collect(), - dm(), - ); - assert_eq!(task.get_due(), Some(test_time)) - } - - #[test] - fn test_get_invalid_due() { - let task = Task::new( - Uuid::new_v4(), - vec![(String::from("due"), String::from("invalid"))] - .drain(..) - .collect(), - dm(), - ); - assert_eq!(task.get_due(), None); - } - - #[test] - fn test_add_due() { - let test_time = Utc.ymd(2033, 1, 1).and_hms(0, 0, 0); - with_mut_task(|mut task| { - assert_eq!(task.get_due(), None); - task.set_due(Some(test_time)).unwrap(); - assert_eq!(task.get_due(), Some(test_time)) - }); - } - - #[test] - fn test_remove_due() { - let test_time = Utc.ymd(2033, 1, 1).and_hms(0, 0, 0); - with_mut_task(|mut task| { - task.set_due(Some(test_time)).unwrap(); - task.reload().unwrap(); - assert!(task.taskmap.contains_key("due")); - task.set_due(None).unwrap(); - assert!(!task.taskmap.contains_key("due")); - }); - } - - #[test] - fn test_get_priority_default() { - let task = Task::new(Uuid::new_v4(), TaskMap::new(), dm()); - assert_eq!(task.get_priority(), ""); - } - - #[test] - fn test_get_annotations() { - let task = Task::new( - Uuid::new_v4(), - vec![ - ( - String::from("annotation_1635301873"), - String::from("left message"), - ), - ( - String::from("annotation_1635301883"), - String::from("left another message"), - ), - (String::from("annotation_"), String::from("invalid")), - (String::from("annotation_abcde"), String::from("invalid")), - ] - .drain(..) - .collect(), - dm(), - ); - - let mut anns: Vec<_> = task.get_annotations().collect(); - anns.sort(); - assert_eq!( - anns, - vec![ - Annotation { - entry: Utc.timestamp(1635301873, 0), - description: "left message".into() - }, - Annotation { - entry: Utc.timestamp(1635301883, 0), - description: "left another message".into() - } - ] - ); - } - - #[test] - fn test_add_annotation() { - with_mut_task(|mut task| { - task.add_annotation(Annotation { - entry: Utc.timestamp(1635301900, 0), - description: "right message".into(), - }) - .unwrap(); - let k = "annotation_1635301900"; - assert_eq!(task.taskmap[k], "right message".to_owned()); - task.reload().unwrap(); - assert_eq!(task.taskmap[k], "right message".to_owned()); - // adding with same time overwrites.. - task.add_annotation(Annotation { - entry: Utc.timestamp(1635301900, 0), - description: "right message 2".into(), - }) - .unwrap(); - assert_eq!(task.taskmap[k], "right message 2".to_owned()); - }); - } - - #[test] - fn test_remove_annotation() { - with_mut_task(|mut task| { - task.set_string("annotation_1635301873", Some("left message".into())) - .unwrap(); - task.set_string("annotation_1635301883", Some("left another message".into())) - .unwrap(); - - task.remove_annotation(Utc.timestamp(1635301873, 0)) - .unwrap(); - - task.reload().unwrap(); - - let mut anns: Vec<_> = task.get_annotations().collect(); - anns.sort(); - assert_eq!( - anns, - vec![Annotation { - entry: Utc.timestamp(1635301883, 0), - description: "left another message".into() - }] - ); - }); - } - - #[test] - fn test_set_get_priority() { - with_mut_task(|mut task| { - assert_eq!(task.get_priority(), ""); - task.set_priority("H".into()).unwrap(); - assert_eq!(task.get_priority(), "H"); - }); - } - - #[test] - fn test_set_status_pending() { - with_mut_task(|mut task| { - task.done().unwrap(); - - task.set_status(Status::Pending).unwrap(); - assert_eq!(task.get_status(), Status::Pending); - assert!(!task.taskmap.contains_key("end")); - assert!(task.has_tag(&stag(SyntheticTag::Pending))); - assert!(!task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_set_status_recurring() { - with_mut_task(|mut task| { - task.done().unwrap(); - - task.set_status(Status::Recurring).unwrap(); - assert_eq!(task.get_status(), Status::Recurring); - assert!(!task.taskmap.contains_key("end")); - assert!(!task.has_tag(&stag(SyntheticTag::Pending))); // recurring is not +PENDING - assert!(!task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_set_status_completed() { - with_mut_task(|mut task| { - task.set_status(Status::Completed).unwrap(); - assert_eq!(task.get_status(), Status::Completed); - assert!(task.taskmap.contains_key("end")); - assert!(!task.has_tag(&stag(SyntheticTag::Pending))); - assert!(task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_set_status_deleted() { - with_mut_task(|mut task| { - task.set_status(Status::Deleted).unwrap(); - assert_eq!(task.get_status(), Status::Deleted); - assert!(task.taskmap.contains_key("end")); - assert!(!task.has_tag(&stag(SyntheticTag::Pending))); - assert!(!task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_set_get_value() { - with_mut_task(|mut task| { - let property = "property-name"; - task.set_value(property, Some("value".into())).unwrap(); - assert_eq!(task.get_value(property), Some("value")); - task.set_value(property, None).unwrap(); - assert_eq!(task.get_value(property), None); - }); - } - - #[test] - fn test_start() { - with_mut_task(|mut task| { - task.start().unwrap(); - assert!(task.taskmap.contains_key("start")); - - task.reload().unwrap(); - assert!(task.taskmap.contains_key("start")); - - // second start doesn't change anything.. - task.start().unwrap(); - assert!(task.taskmap.contains_key("start")); - - task.reload().unwrap(); - assert!(task.taskmap.contains_key("start")); - }); - } - - #[test] - fn test_stop() { - with_mut_task(|mut task| { - task.start().unwrap(); - task.stop().unwrap(); - assert!(!task.taskmap.contains_key("start")); - - task.reload().unwrap(); - assert!(!task.taskmap.contains_key("start")); - - // redundant call does nothing.. - task.stop().unwrap(); - assert!(!task.taskmap.contains_key("start")); - - task.reload().unwrap(); - assert!(!task.taskmap.contains_key("start")); - }); - } - - #[test] - fn test_done() { - with_mut_task(|mut task| { - task.done().unwrap(); - assert_eq!(task.get_status(), Status::Completed); - assert!(task.taskmap.contains_key("end")); - assert!(task.has_tag(&stag(SyntheticTag::Completed))); - - // redundant call does nothing.. - task.done().unwrap(); - assert_eq!(task.get_status(), Status::Completed); - assert!(task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_delete() { - with_mut_task(|mut task| { - task.delete().unwrap(); - assert_eq!(task.get_status(), Status::Deleted); - assert!(task.taskmap.contains_key("end")); - assert!(!task.has_tag(&stag(SyntheticTag::Completed))); - - // redundant call does nothing.. - task.delete().unwrap(); - assert_eq!(task.get_status(), Status::Deleted); - assert!(!task.has_tag(&stag(SyntheticTag::Completed))); - }); - } - - #[test] - fn test_add_tags() { - with_mut_task(|mut task| { - task.add_tag(&utag("abc")).unwrap(); - assert!(task.taskmap.contains_key("tag_abc")); - task.reload().unwrap(); - assert!(task.taskmap.contains_key("tag_abc")); - // redundant add has no effect.. - task.add_tag(&utag("abc")).unwrap(); - assert!(task.taskmap.contains_key("tag_abc")); - }); - } - - #[test] - fn test_remove_tags() { - with_mut_task(|mut task| { - task.add_tag(&utag("abc")).unwrap(); - task.reload().unwrap(); - assert!(task.taskmap.contains_key("tag_abc")); - - task.remove_tag(&utag("abc")).unwrap(); - assert!(!task.taskmap.contains_key("tag_abc")); - // redundant remove has no effect.. - task.remove_tag(&utag("abc")).unwrap(); - assert!(!task.taskmap.contains_key("tag_abc")); - }); - } - - #[test] - fn test_get_udas() { - let task = Task::new( - Uuid::new_v4(), - vec![ - ("description".into(), "not a uda".into()), - ("modified".into(), "not a uda".into()), - ("start".into(), "not a uda".into()), - ("status".into(), "not a uda".into()), - ("wait".into(), "not a uda".into()), - ("start".into(), "not a uda".into()), - ("tag_abc".into(), "not a uda".into()), - ("dep_1234".into(), "not a uda".into()), - ("annotation_1234".into(), "not a uda".into()), - ("githubid".into(), "123".into()), - ("jira.url".into(), "h://x".into()), - ] - .drain(..) - .collect(), - dm(), - ); - - let mut udas: Vec<_> = task.get_udas().collect(); - udas.sort_unstable(); - assert_eq!( - udas, - vec![(("", "githubid"), "123"), (("jira", "url"), "h://x")] - ); - } - - #[test] - fn test_get_uda() { - let task = Task::new( - Uuid::new_v4(), - vec![ - ("description".into(), "not a uda".into()), - ("githubid".into(), "123".into()), - ("jira.url".into(), "h://x".into()), - ] - .drain(..) - .collect(), - dm(), - ); - - assert_eq!(task.get_uda("", "description"), None); // invalid UDA - assert_eq!(task.get_uda("", "githubid"), Some("123")); - assert_eq!(task.get_uda("jira", "url"), Some("h://x")); - assert_eq!(task.get_uda("bugzilla", "url"), None); - } - - #[test] - fn test_get_legacy_uda() { - let task = Task::new( - Uuid::new_v4(), - vec![ - ("description".into(), "not a uda".into()), - ("dep_1234".into(), "not a uda".into()), - ("githubid".into(), "123".into()), - ("jira.url".into(), "h://x".into()), - ] - .drain(..) - .collect(), - dm(), - ); - - assert_eq!(task.get_legacy_uda("description"), None); // invalid UDA - assert_eq!(task.get_legacy_uda("dep_1234"), None); // invalid UDA - assert_eq!(task.get_legacy_uda("githubid"), Some("123")); - assert_eq!(task.get_legacy_uda("jira.url"), Some("h://x")); - assert_eq!(task.get_legacy_uda("bugzilla.url"), None); - } - - #[test] - fn test_set_uda() { - with_mut_task(|mut task| { - task.set_uda("jira", "url", "h://y").unwrap(); - let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![(("jira", "url"), "h://y")]); - - task.set_uda("", "jiraid", "TW-1234").unwrap(); - - let mut udas: Vec<_> = task.get_udas().collect(); - udas.sort_unstable(); - assert_eq!( - udas, - vec![(("", "jiraid"), "TW-1234"), (("jira", "url"), "h://y")] - ); - }) - } - - #[test] - fn test_set_legacy_uda() { - with_mut_task(|mut task| { - task.set_legacy_uda("jira.url", "h://y").unwrap(); - let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![(("jira", "url"), "h://y")]); - - task.set_legacy_uda("jiraid", "TW-1234").unwrap(); - - let mut udas: Vec<_> = task.get_udas().collect(); - udas.sort_unstable(); - assert_eq!( - udas, - vec![(("", "jiraid"), "TW-1234"), (("jira", "url"), "h://y")] - ); - }) - } - - #[test] - fn test_set_uda_invalid() { - with_mut_task(|mut task| { - assert!(task.set_uda("", "modified", "123").is_err()); - assert!(task.set_uda("", "tag_abc", "123").is_err()); - assert!(task.set_legacy_uda("modified", "123").is_err()); - assert!(task.set_legacy_uda("tag_abc", "123").is_err()); - }) - } - - #[test] - fn test_remove_uda() { - with_mut_task(|mut task| { - task.set_string("github.id", Some("123".into())).unwrap(); - task.remove_uda("github", "id").unwrap(); - - let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![]); - }) - } - - #[test] - fn test_remove_legacy_uda() { - with_mut_task(|mut task| { - task.set_string("githubid", Some("123".into())).unwrap(); - task.remove_legacy_uda("githubid").unwrap(); - - let udas: Vec<_> = task.get_udas().collect(); - assert_eq!(udas, vec![]); - }) - } - - #[test] - fn test_remove_uda_invalid() { - with_mut_task(|mut task| { - assert!(task.remove_uda("", "modified").is_err()); - assert!(task.remove_uda("", "tag_abc").is_err()); - assert!(task.remove_legacy_uda("modified").is_err()); - assert!(task.remove_legacy_uda("tag_abc").is_err()); - }) - } - - #[test] - fn test_dependencies() { - with_mut_task(|mut task| { - assert_eq!(task.get_dependencies().collect::>(), vec![]); - let dep1 = Uuid::new_v4(); - let dep2 = Uuid::new_v4(); - - task.add_dependency(dep1).unwrap(); - assert_eq!(task.get_dependencies().collect::>(), vec![dep1]); - - task.add_dependency(dep1).unwrap(); // add twice is ok - task.add_dependency(dep2).unwrap(); - let deps = task.get_dependencies().collect::>(); - assert!(deps.contains(&dep1)); - assert!(deps.contains(&dep2)); - - task.remove_dependency(dep1).unwrap(); - assert_eq!(task.get_dependencies().collect::>(), vec![dep2]); - }) - } - - #[test] - fn dependencies_tags() { - let mut rep = Replica::new_inmemory(); - let uuid1; - let uuid2; - { - let t1 = rep.new_task(Status::Pending, "1".into()).unwrap(); - uuid1 = t1.get_uuid(); - let t2 = rep.new_task(Status::Pending, "2".into()).unwrap(); - uuid2 = t2.get_uuid(); - - let mut t1 = t1.into_mut(&mut rep); - t1.add_dependency(t2.get_uuid()).unwrap(); - } - - // force-refresh depmap - rep.dependency_map(true).unwrap(); - - let t1 = rep.get_task(uuid1).unwrap().unwrap(); - let t2 = rep.get_task(uuid2).unwrap().unwrap(); - assert!(t1.has_tag(&stag(SyntheticTag::Blocked))); - assert!(!t1.has_tag(&stag(SyntheticTag::Unblocked))); - assert!(!t1.has_tag(&stag(SyntheticTag::Blocking))); - assert!(!t2.has_tag(&stag(SyntheticTag::Blocked))); - assert!(t2.has_tag(&stag(SyntheticTag::Unblocked))); - assert!(t2.has_tag(&stag(SyntheticTag::Blocking))); - } - - #[test] - fn set_value_modified() { - with_mut_task(|mut task| { - // set the modified property to something in the past.. - task.set_value("modified", Some("1671820000".into())) - .unwrap(); - // update another property - task.set_description("fun times".into()).unwrap(); - // verify the modified property was not updated - assert_eq!(task.get_value("modified").unwrap(), "1671820000") - }) - } -} diff --git a/taskchampion/taskchampion/src/task/time.rs b/taskchampion/taskchampion/src/task/time.rs deleted file mode 100644 index 928da2a6a..000000000 --- a/taskchampion/taskchampion/src/task/time.rs +++ /dev/null @@ -1,11 +0,0 @@ -use chrono::{offset::LocalResult, DateTime, TimeZone, Utc}; - -pub type Timestamp = DateTime; - -pub fn utc_timestamp(secs: i64) -> Timestamp { - match Utc.timestamp_opt(secs, 0) { - LocalResult::Single(tz) => tz, - // The other two variants are None and Ambiguous, which both are caused by DST. - _ => unreachable!("We're requesting UTC so daylight saving time isn't a factor."), - } -} diff --git a/taskchampion/taskchampion/src/taskdb/apply.rs b/taskchampion/taskchampion/src/taskdb/apply.rs deleted file mode 100644 index 0cea9f28c..000000000 --- a/taskchampion/taskchampion/src/taskdb/apply.rs +++ /dev/null @@ -1,406 +0,0 @@ -use crate::errors::{Error, Result}; -use crate::server::SyncOp; -use crate::storage::{ReplicaOp, StorageTxn, TaskMap}; - -/// Apply the given SyncOp to the replica, updating both the task data and adding a -/// ReplicaOp to the list of operations. Returns the TaskMap of the task after the -/// operation has been applied (or an empty TaskMap for Delete). It is not an error -/// to create an existing task, nor to delete a nonexistent task. -pub(super) fn apply_and_record(txn: &mut dyn StorageTxn, op: SyncOp) -> Result { - match op { - SyncOp::Create { uuid } => { - let created = txn.create_task(uuid)?; - if created { - txn.add_operation(ReplicaOp::Create { uuid })?; - txn.commit()?; - Ok(TaskMap::new()) - } else { - Ok(txn - .get_task(uuid)? - .expect("create_task failed but task does not exist")) - } - } - SyncOp::Delete { uuid } => { - let task = txn.get_task(uuid)?; - if let Some(task) = task { - txn.delete_task(uuid)?; - txn.add_operation(ReplicaOp::Delete { - uuid, - old_task: task, - })?; - txn.commit()?; - Ok(TaskMap::new()) - } else { - Ok(TaskMap::new()) - } - } - SyncOp::Update { - uuid, - property, - value, - timestamp, - } => { - let task = txn.get_task(uuid)?; - if let Some(mut task) = task { - let old_value = task.get(&property).cloned(); - if let Some(ref v) = value { - task.insert(property.clone(), v.clone()); - } else { - task.remove(&property); - } - txn.set_task(uuid, task.clone())?; - txn.add_operation(ReplicaOp::Update { - uuid, - property, - old_value, - value, - timestamp, - })?; - txn.commit()?; - Ok(task) - } else { - Err(Error::Database(format!("Task {} does not exist", uuid))) - } - } - } -} - -/// Apply an op to the TaskDb's set of tasks (without recording it in the list of operations) -pub(super) fn apply_op(txn: &mut dyn StorageTxn, op: &SyncOp) -> Result<()> { - // TODO: test - // TODO: it'd be nice if this was integrated into apply() somehow, but that clones TaskMaps - // unnecessariliy - match op { - SyncOp::Create { uuid } => { - // insert if the task does not already exist - if !txn.create_task(*uuid)? { - return Err(Error::Database(format!("Task {} already exists", uuid))); - } - } - SyncOp::Delete { ref uuid } => { - if !txn.delete_task(*uuid)? { - return Err(Error::Database(format!("Task {} does not exist", uuid))); - } - } - SyncOp::Update { - ref uuid, - ref property, - ref value, - timestamp: _, - } => { - // update if this task exists, otherwise ignore - if let Some(mut task) = txn.get_task(*uuid)? { - match value { - Some(ref val) => task.insert(property.to_string(), val.clone()), - None => task.remove(property), - }; - txn.set_task(*uuid, task)?; - } else { - return Err(Error::Database(format!("Task {} does not exist", uuid))); - } - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::storage::TaskMap; - use crate::taskdb::TaskDb; - use chrono::Utc; - use pretty_assertions::assert_eq; - use std::collections::HashMap; - use uuid::Uuid; - - #[test] - fn test_apply_create() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = SyncOp::Create { uuid }; - - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op)?; - assert_eq!(taskmap.len(), 0); - txn.commit()?; - } - - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); - assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); - Ok(()) - } - - #[test] - fn test_apply_create_exists() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - { - let mut txn = db.storage.txn()?; - txn.create_task(uuid)?; - let mut taskmap = TaskMap::new(); - taskmap.insert("foo".into(), "bar".into()); - txn.set_task(uuid, taskmap)?; - txn.commit()?; - } - - let op = SyncOp::Create { uuid }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op)?; - - assert_eq!(taskmap.len(), 1); - assert_eq!(taskmap.get("foo").unwrap(), "bar"); - - txn.commit()?; - } - - // create did not delete the old task.. - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("foo".into(), "bar".into())])] - ); - // create was done "manually" above, and no new op was added - assert_eq!(db.operations(), vec![]); - Ok(()) - } - - #[test] - fn test_apply_create_update() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let now = Utc::now(); - let op1 = SyncOp::Create { uuid }; - - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op1)?; - assert_eq!(taskmap.len(), 0); - txn.commit()?; - } - - let op2 = SyncOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: now, - }; - { - let mut txn = db.storage.txn()?; - let mut taskmap = apply_and_record(txn.as_mut(), op2)?; - assert_eq!( - taskmap.drain().collect::>(), - vec![("title".into(), "my task".into())] - ); - txn.commit()?; - } - - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("title".into(), "my task".into())])] - ); - assert_eq!( - db.operations(), - vec![ - ReplicaOp::Create { uuid }, - ReplicaOp::Update { - uuid, - property: "title".into(), - old_value: None, - value: Some("my task".into()), - timestamp: now - } - ] - ); - - Ok(()) - } - - #[test] - fn test_apply_create_update_delete_prop() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let now = Utc::now(); - let op1 = SyncOp::Create { uuid }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op1)?; - assert_eq!(taskmap.len(), 0); - txn.commit()?; - } - - let op2 = SyncOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: now, - }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op2)?; - assert_eq!(taskmap.get("title"), Some(&"my task".to_owned())); - txn.commit()?; - } - - let op3 = SyncOp::Update { - uuid, - property: String::from("priority"), - value: Some("H".into()), - timestamp: now, - }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op3)?; - assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); - txn.commit()?; - } - - let op4 = SyncOp::Update { - uuid, - property: String::from("title"), - value: None, - timestamp: now, - }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op4)?; - assert_eq!(taskmap.get("title"), None); - assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); - txn.commit()?; - } - - let mut exp = HashMap::new(); - let mut task = HashMap::new(); - task.insert(String::from("priority"), String::from("H")); - exp.insert(uuid, task); - assert_eq!( - db.sorted_tasks(), - vec![(uuid, vec![("priority".into(), "H".into())])] - ); - assert_eq!( - db.operations(), - vec![ - ReplicaOp::Create { uuid }, - ReplicaOp::Update { - uuid, - property: "title".into(), - old_value: None, - value: Some("my task".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid, - property: "priority".into(), - old_value: None, - value: Some("H".into()), - timestamp: now, - }, - ReplicaOp::Update { - uuid, - property: "title".into(), - old_value: Some("my task".into()), - value: None, - timestamp: now, - } - ] - ); - - Ok(()) - } - - #[test] - fn test_apply_update_does_not_exist() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = SyncOp::Update { - uuid, - property: String::from("title"), - value: Some("my task".into()), - timestamp: Utc::now(), - }; - { - let mut txn = db.storage.txn()?; - assert_eq!( - apply_and_record(txn.as_mut(), op) - .err() - .unwrap() - .to_string(), - format!("Task Database Error: Task {} does not exist", uuid) - ); - txn.commit()?; - } - - Ok(()) - } - - #[test] - fn test_apply_create_delete() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let now = Utc::now(); - - let op1 = SyncOp::Create { uuid }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op1)?; - assert_eq!(taskmap.len(), 0); - } - - let op2 = SyncOp::Update { - uuid, - property: String::from("priority"), - value: Some("H".into()), - timestamp: now, - }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op2)?; - assert_eq!(taskmap.get("priority"), Some(&"H".to_owned())); - txn.commit()?; - } - - let op3 = SyncOp::Delete { uuid }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op3)?; - assert_eq!(taskmap.len(), 0); - txn.commit()?; - } - - assert_eq!(db.sorted_tasks(), vec![]); - let mut old_task = TaskMap::new(); - old_task.insert("priority".into(), "H".into()); - assert_eq!( - db.operations(), - vec![ - ReplicaOp::Create { uuid }, - ReplicaOp::Update { - uuid, - property: "priority".into(), - old_value: None, - value: Some("H".into()), - timestamp: now, - }, - ReplicaOp::Delete { uuid, old_task }, - ] - ); - - Ok(()) - } - - #[test] - fn test_apply_delete_not_present() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = SyncOp::Delete { uuid }; - { - let mut txn = db.storage.txn()?; - let taskmap = apply_and_record(txn.as_mut(), op)?; - assert_eq!(taskmap.len(), 0); - txn.commit()?; - } - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/taskdb/mod.rs b/taskchampion/taskchampion/src/taskdb/mod.rs deleted file mode 100644 index 6418f9279..000000000 --- a/taskchampion/taskchampion/src/taskdb/mod.rs +++ /dev/null @@ -1,297 +0,0 @@ -use crate::errors::Result; -use crate::server::{Server, SyncOp}; -use crate::storage::{ReplicaOp, Storage, TaskMap}; -use uuid::Uuid; - -mod apply; -mod snapshot; -mod sync; -pub mod undo; -mod working_set; - -/// A TaskDb is the backend for a replica. It manages the storage, operations, synchronization, -/// and so on, and all the invariants that come with it. It leaves the meaning of particular task -/// properties to the replica and task implementations. -pub struct TaskDb { - storage: Box, -} - -impl TaskDb { - /// Create a new TaskDb with the given backend storage - pub fn new(storage: Box) -> TaskDb { - TaskDb { storage } - } - - #[cfg(test)] - pub fn new_inmemory() -> TaskDb { - #[cfg(test)] - use crate::storage::InMemoryStorage; - - TaskDb::new(Box::new(InMemoryStorage::new())) - } - - /// Apply an operation to the TaskDb. This will update the set of tasks and add a ReplicaOp to - /// the set of operations in the TaskDb, and return the TaskMap containing the resulting task's - /// properties (or an empty TaskMap for deletion). - /// - /// Aside from synchronization operations, this is the only way to modify the TaskDb. In cases - /// where an operation does not make sense, this function will do nothing and return an error - /// (but leave the TaskDb in a consistent state). - pub fn apply(&mut self, op: SyncOp) -> Result { - let mut txn = self.storage.txn()?; - apply::apply_and_record(txn.as_mut(), op) - } - - /// Add an UndoPoint operation to the list of replica operations. - pub fn add_undo_point(&mut self) -> Result<()> { - let mut txn = self.storage.txn()?; - txn.add_operation(ReplicaOp::UndoPoint)?; - txn.commit() - } - - /// Get all tasks. - pub fn all_tasks(&mut self) -> Result> { - let mut txn = self.storage.txn()?; - txn.all_tasks() - } - - /// Get the UUIDs of all tasks - pub fn all_task_uuids(&mut self) -> Result> { - let mut txn = self.storage.txn()?; - txn.all_task_uuids() - } - - /// Get the working set - pub fn working_set(&mut self) -> Result>> { - let mut txn = self.storage.txn()?; - txn.get_working_set() - } - - /// Get a single task, by uuid. - pub fn get_task(&mut self, uuid: Uuid) -> Result> { - let mut txn = self.storage.txn()?; - txn.get_task(uuid) - } - - /// Rebuild the working set using a function to identify tasks that should be in the set. This - /// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that - /// are not already in the working set but should be. The rebuild occurs in a single - /// trasnsaction against the storage backend. - pub fn rebuild_working_set(&mut self, in_working_set: F, renumber: bool) -> Result<()> - where - F: Fn(&TaskMap) -> bool, - { - working_set::rebuild(self.storage.txn()?.as_mut(), in_working_set, renumber) - } - - /// Add the given uuid to the working set and return its index; if it is already in the working - /// set, its index is returned. This does *not* renumber any existing tasks. - pub fn add_to_working_set(&mut self, uuid: Uuid) -> Result { - let mut txn = self.storage.txn()?; - // search for an existing entry for this task.. - for (i, elt) in txn.get_working_set()?.iter().enumerate() { - if *elt == Some(uuid) { - // (note that this drops the transaction with no changes made) - return Ok(i); - } - } - // and if not found, add one - let i = txn.add_to_working_set(uuid)?; - txn.commit()?; - Ok(i) - } - - /// Sync to the given server, pulling remote changes and pushing local changes. - /// - /// If `avoid_snapshots` is true, the sync operations produces a snapshot only when the server - /// indicate it is urgent (snapshot urgency "high"). This allows time for other replicas to - /// create a snapshot before this one does. - /// - /// Set this to true on systems more constrained in CPU, memory, or bandwidth than a typical desktop - /// system - pub fn sync(&mut self, server: &mut Box, avoid_snapshots: bool) -> Result<()> { - let mut txn = self.storage.txn()?; - sync::sync(server, txn.as_mut(), avoid_snapshots) - } - - /// Return undo local operations until the most recent UndoPoint, returning an empty Vec if there are no - /// local operations to undo. - pub fn get_undo_ops(&mut self) -> Result> { - let mut txn = self.storage.txn()?; - undo::get_undo_ops(txn.as_mut()) - } - - /// Undo local operations in storage, returning a boolean indicating success. - pub fn commit_undo_ops(&mut self, undo_ops: Vec) -> Result { - let mut txn = self.storage.txn()?; - undo::commit_undo_ops(txn.as_mut(), undo_ops) - } - - /// Get the number of un-synchronized operations in storage, excluding undo - /// operations. - pub fn num_operations(&mut self) -> Result { - let mut txn = self.storage.txn().unwrap(); - Ok(txn - .operations()? - .iter() - .filter(|o| !o.is_undo_point()) - .count()) - } - - /// Get the number of (un-synchronized) undo points in storage. - pub fn num_undo_points(&mut self) -> Result { - let mut txn = self.storage.txn().unwrap(); - Ok(txn - .operations()? - .iter() - .filter(|o| o.is_undo_point()) - .count()) - } - - // functions for supporting tests - - #[cfg(test)] - pub(crate) fn sorted_tasks(&mut self) -> Vec<(Uuid, Vec<(String, String)>)> { - let mut res: Vec<(Uuid, Vec<(String, String)>)> = self - .all_tasks() - .unwrap() - .iter() - .map(|(u, t)| { - let mut t = t - .iter() - .map(|(p, v)| (p.clone(), v.clone())) - .collect::>(); - t.sort(); - (*u, t) - }) - .collect(); - res.sort(); - res - } - - #[cfg(test)] - pub(crate) fn operations(&mut self) -> Vec { - let mut txn = self.storage.txn().unwrap(); - txn.operations().unwrap().to_vec() - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::server::test::TestServer; - use crate::storage::{InMemoryStorage, ReplicaOp}; - use chrono::Utc; - use pretty_assertions::assert_eq; - use proptest::prelude::*; - use uuid::Uuid; - - #[test] - fn test_apply() { - // this verifies that the operation is both applied and included in the list of - // operations; more detailed tests are in the `apply` module. - let mut db = TaskDb::new_inmemory(); - let uuid = Uuid::new_v4(); - let op = SyncOp::Create { uuid }; - db.apply(op).unwrap(); - - assert_eq!(db.sorted_tasks(), vec![(uuid, vec![]),]); - assert_eq!(db.operations(), vec![ReplicaOp::Create { uuid }]); - } - - #[test] - fn test_add_undo_point() { - let mut db = TaskDb::new_inmemory(); - db.add_undo_point().unwrap(); - assert_eq!(db.operations(), vec![ReplicaOp::UndoPoint]); - } - - #[test] - fn test_num_operations() { - let mut db = TaskDb::new_inmemory(); - db.apply(SyncOp::Create { - uuid: Uuid::new_v4(), - }) - .unwrap(); - db.add_undo_point().unwrap(); - db.apply(SyncOp::Create { - uuid: Uuid::new_v4(), - }) - .unwrap(); - assert_eq!(db.num_operations().unwrap(), 2); - } - - #[test] - fn test_num_undo_points() { - let mut db = TaskDb::new_inmemory(); - db.add_undo_point().unwrap(); - assert_eq!(db.num_undo_points().unwrap(), 1); - db.add_undo_point().unwrap(); - assert_eq!(db.num_undo_points().unwrap(), 2); - } - - fn newdb() -> TaskDb { - TaskDb::new(Box::new(InMemoryStorage::new())) - } - - #[derive(Debug)] - enum Action { - Op(SyncOp), - Sync, - } - - fn action_sequence_strategy() -> impl Strategy> { - // Create, Update, Delete, or Sync on client 1, 2, .., followed by a round of syncs - "([CUDS][123])*S1S2S3S1S2".prop_map(|seq| { - let uuid = Uuid::parse_str("83a2f9ef-f455-4195-b92e-a54c161eebfc").unwrap(); - seq.as_bytes() - .chunks(2) - .map(|action_on| { - let action = match action_on[0] { - b'C' => Action::Op(SyncOp::Create { uuid }), - b'U' => Action::Op(SyncOp::Update { - uuid, - property: "title".into(), - value: Some("foo".into()), - timestamp: Utc::now(), - }), - b'D' => Action::Op(SyncOp::Delete { uuid }), - b'S' => Action::Sync, - _ => unreachable!(), - }; - let acton = action_on[1] - b'1'; - (action, acton) - }) - .collect::>() - }) - } - - proptest! { - #[test] - // check that various sequences of operations on mulitple db's do not get the db's into an - // incompatible state. The main concern here is that there might be a sequence of create - // and delete operations that results in a task existing in one TaskDb but not existing in - // another. So, the generated sequences focus on a single task UUID. - fn transform_sequences_of_operations(action_sequence in action_sequence_strategy()) { - let mut server: Box = Box::new(TestServer::new()); - let mut dbs = [newdb(), newdb(), newdb()]; - - for (action, db) in action_sequence { - println!("{:?} on db {}", action, db); - - let db = &mut dbs[db as usize]; - match action { - Action::Op(op) => { - if let Err(e) = db.apply(op) { - println!(" {:?} (ignored)", e); - } - }, - Action::Sync => db.sync(&mut server, false).unwrap(), - } - } - - assert_eq!(dbs[0].sorted_tasks(), dbs[0].sorted_tasks()); - assert_eq!(dbs[1].sorted_tasks(), dbs[2].sorted_tasks()); - } - } -} diff --git a/taskchampion/taskchampion/src/taskdb/snapshot.rs b/taskchampion/taskchampion/src/taskdb/snapshot.rs deleted file mode 100644 index 3bff2eb2a..000000000 --- a/taskchampion/taskchampion/src/taskdb/snapshot.rs +++ /dev/null @@ -1,181 +0,0 @@ -use crate::errors::{Error, Result}; -use crate::storage::{StorageTxn, TaskMap, VersionId}; -use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; -use serde::de::{Deserialize, Deserializer, MapAccess, Visitor}; -use serde::ser::{Serialize, SerializeMap, Serializer}; -use std::fmt; -use uuid::Uuid; - -/// A newtype to wrap the result of [`crate::storage::StorageTxn::all_tasks`] -pub(super) struct SnapshotTasks(Vec<(Uuid, TaskMap)>); - -impl Serialize for SnapshotTasks { - fn serialize(&self, serializer: S) -> std::result::Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(self.0.len()))?; - for (k, v) in &self.0 { - map.serialize_entry(k, v)?; - } - map.end() - } -} - -struct TaskDbVisitor; - -impl<'de> Visitor<'de> for TaskDbVisitor { - type Value = SnapshotTasks; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a map representing a task snapshot") - } - - fn visit_map(self, mut access: M) -> std::result::Result - where - M: MapAccess<'de>, - { - let mut map = SnapshotTasks(Vec::with_capacity(access.size_hint().unwrap_or(0))); - - while let Some((key, value)) = access.next_entry()? { - map.0.push((key, value)); - } - - Ok(map) - } -} - -impl<'de> Deserialize<'de> for SnapshotTasks { - fn deserialize(deserializer: D) -> std::result::Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(TaskDbVisitor) - } -} - -impl SnapshotTasks { - pub(super) fn encode(&self) -> Result> { - let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default()); - serde_json::to_writer(&mut encoder, &self)?; - Ok(encoder.finish()?) - } - - pub(super) fn decode(snapshot: &[u8]) -> Result { - let decoder = ZlibDecoder::new(snapshot); - Ok(serde_json::from_reader(decoder)?) - } - - pub(super) fn into_inner(self) -> Vec<(Uuid, TaskMap)> { - self.0 - } -} - -/// Generate a snapshot (compressed, unencrypted) for the current state of the taskdb in the given -/// storage. -pub(super) fn make_snapshot(txn: &mut dyn StorageTxn) -> Result> { - let all_tasks = SnapshotTasks(txn.all_tasks()?); - all_tasks.encode() -} - -/// Apply the given snapshot (compressed, unencrypted) to the taskdb's storage. -pub(super) fn apply_snapshot( - txn: &mut dyn StorageTxn, - version: VersionId, - snapshot: &[u8], -) -> Result<()> { - let all_tasks = SnapshotTasks::decode(snapshot)?; - - // double-check emptiness - if !txn.is_empty()? { - return Err(Error::Database(String::from( - "Cannot apply snapshot to a non-empty task database", - ))); - } - - for (uuid, task) in all_tasks.into_inner().drain(..) { - txn.set_task(uuid, task)?; - } - txn.set_base_version(version)?; - - Ok(()) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::storage::{InMemoryStorage, Storage, TaskMap}; - use pretty_assertions::assert_eq; - - #[test] - fn test_serialize_empty() -> Result<()> { - let empty = SnapshotTasks(vec![]); - assert_eq!(serde_json::to_vec(&empty)?, b"{}".to_owned()); - Ok(()) - } - - #[test] - fn test_serialize_tasks() -> Result<()> { - let u = Uuid::new_v4(); - let m: TaskMap = vec![("description".to_owned(), "my task".to_owned())] - .drain(..) - .collect(); - let all_tasks = SnapshotTasks(vec![(u, m)]); - assert_eq!( - serde_json::to_vec(&all_tasks)?, - format!("{{\"{}\":{{\"description\":\"my task\"}}}}", u).into_bytes(), - ); - Ok(()) - } - - #[test] - fn test_round_trip() -> Result<()> { - let mut storage = InMemoryStorage::new(); - let version = Uuid::new_v4(); - - let task1 = ( - Uuid::new_v4(), - vec![("description".to_owned(), "one".to_owned())] - .drain(..) - .collect::(), - ); - let task2 = ( - Uuid::new_v4(), - vec![("description".to_owned(), "two".to_owned())] - .drain(..) - .collect::(), - ); - - { - let mut txn = storage.txn()?; - txn.set_task(task1.0, task1.1.clone())?; - txn.set_task(task2.0, task2.1.clone())?; - txn.commit()?; - } - - let snap = { - let mut txn = storage.txn()?; - make_snapshot(txn.as_mut())? - }; - - // apply that snapshot to a fresh bit of fake - let mut storage = InMemoryStorage::new(); - { - let mut txn = storage.txn()?; - apply_snapshot(txn.as_mut(), version, &snap)?; - txn.commit()? - } - - { - let mut txn = storage.txn()?; - assert_eq!(txn.get_task(task1.0)?, Some(task1.1)); - assert_eq!(txn.get_task(task2.0)?, Some(task2.1)); - assert_eq!(txn.all_tasks()?.len(), 2); - assert_eq!(txn.base_version()?, version); - assert_eq!(txn.operations()?.len(), 0); - assert_eq!(txn.get_working_set()?.len(), 1); - } - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/taskdb/sync.rs b/taskchampion/taskchampion/src/taskdb/sync.rs deleted file mode 100644 index 95e2938c4..000000000 --- a/taskchampion/taskchampion/src/taskdb/sync.rs +++ /dev/null @@ -1,386 +0,0 @@ -use super::{apply, snapshot}; -use crate::errors::Result; -use crate::server::{AddVersionResult, GetVersionResult, Server, SnapshotUrgency, SyncOp}; -use crate::storage::StorageTxn; -use crate::Error; -use log::{info, trace, warn}; -use serde::{Deserialize, Serialize}; -use std::str; - -#[derive(Serialize, Deserialize, Debug)] -struct Version { - operations: Vec, -} - -/// Sync to the given server, pulling remote changes and pushing local changes. -pub(super) fn sync( - server: &mut Box, - txn: &mut dyn StorageTxn, - avoid_snapshots: bool, -) -> Result<()> { - // if this taskdb is entirely empty, then start by getting and applying a snapshot - if txn.is_empty()? { - trace!("storage is empty; attempting to apply a snapshot"); - if let Some((version, snap)) = server.get_snapshot()? { - snapshot::apply_snapshot(txn, version, snap.as_ref())?; - trace!("applied snapshot for version {}", version); - } - } - - // retry synchronizing until the server accepts our version (this allows for races between - // replicas trying to sync to the same server). If the server insists on the same base - // version twice, then we have diverged. - let mut requested_parent_version_id = None; - loop { - trace!("beginning sync outer loop"); - let mut base_version_id = txn.base_version()?; - - let mut local_ops: Vec = txn - .operations()? - .drain(..) - .filter_map(|op| op.into_sync()) - .collect(); - - // first pull changes and "rebase" on top of them - loop { - trace!("beginning sync inner loop"); - if let GetVersionResult::Version { - version_id, - history_segment, - .. - } = server.get_child_version(base_version_id)? - { - let version_str = str::from_utf8(&history_segment).unwrap(); - let version: Version = serde_json::from_str(version_str).unwrap(); - - // apply this verison and update base_version in storage - info!("applying version {:?} from server", version_id); - apply_version(txn, &mut local_ops, version)?; - txn.set_base_version(version_id)?; - base_version_id = version_id; - } else { - info!("no child versions of {:?}", base_version_id); - // at the moment, no more child versions, so we can try adding our own - break; - } - } - - if local_ops.is_empty() { - info!("no changes to push to server"); - // nothing to sync back to the server.. - break; - } - - trace!("sending {} operations to the server", local_ops.len()); - - // now make a version of our local changes and push those - let new_version = Version { - operations: local_ops, - }; - let history_segment = serde_json::to_string(&new_version).unwrap().into(); - info!("sending new version to server"); - let (res, snapshot_urgency) = server.add_version(base_version_id, history_segment)?; - match res { - AddVersionResult::Ok(new_version_id) => { - info!("version {:?} received by server", new_version_id); - txn.set_base_version(new_version_id)?; - - // make a snapshot if the server indicates it is urgent enough - let base_urgency = if avoid_snapshots { - SnapshotUrgency::High - } else { - SnapshotUrgency::Low - }; - if snapshot_urgency >= base_urgency { - let snapshot = snapshot::make_snapshot(txn)?; - server.add_snapshot(new_version_id, snapshot)?; - } - - break; - } - AddVersionResult::ExpectedParentVersion(parent_version_id) => { - info!( - "new version rejected; must be based on {:?}", - parent_version_id - ); - if let Some(requested) = requested_parent_version_id { - if parent_version_id == requested { - return Err(Error::OutOfSync); - } - } - requested_parent_version_id = Some(parent_version_id); - } - } - } - - txn.set_operations(vec![])?; - txn.commit()?; - Ok(()) -} - -fn apply_version( - txn: &mut dyn StorageTxn, - local_ops: &mut Vec, - mut version: Version, -) -> Result<()> { - // The situation here is that the server has already applied all server operations, and we - // have already applied all local operations, so states have diverged by several - // operations. We need to figure out what operations to apply locally and on the server in - // order to return to the same state. - // - // Operational transforms provide this on an operation-by-operation basis. To break this - // down, we treat each server operation individually, in order. For each such operation, - // we start in this state: - // - // - // base state-* - // / \-server op - // * * - // local / \ / - // ops * * - // / \ / new - // * * local - // local / \ / ops - // state-* * - // new-\ / - // server op *-new local state - // - // This is slightly complicated by the fact that the transform function can return None, - // indicating no operation is required. If this happens for a local op, we can just omit - // it. If it happens for server op, then we must copy the remaining local ops. - for server_op in version.operations.drain(..) { - trace!( - "rebasing local operations onto server operation {:?}", - server_op - ); - let mut new_local_ops = Vec::with_capacity(local_ops.len()); - let mut svr_op = Some(server_op); - for local_op in local_ops.drain(..) { - if let Some(o) = svr_op { - let (new_server_op, new_local_op) = SyncOp::transform(o, local_op.clone()); - trace!("local operation {:?} -> {:?}", local_op, new_local_op); - svr_op = new_server_op; - if let Some(o) = new_local_op { - new_local_ops.push(o); - } - } else { - trace!( - "local operation {:?} unchanged (server operation consumed)", - local_op - ); - new_local_ops.push(local_op); - } - } - if let Some(o) = svr_op { - if let Err(e) = apply::apply_op(txn, &o) { - warn!("Invalid operation when syncing: {} (ignored)", e); - } - } - *local_ops = new_local_ops; - } - Ok(()) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::server::{test::TestServer, SyncOp}; - use crate::storage::InMemoryStorage; - use crate::taskdb::{snapshot::SnapshotTasks, TaskDb}; - use chrono::Utc; - use pretty_assertions::assert_eq; - use uuid::Uuid; - - fn newdb() -> TaskDb { - TaskDb::new(Box::new(InMemoryStorage::new())) - } - - #[test] - fn test_sync() -> Result<()> { - let mut server: Box = TestServer::new().server(); - - let mut db1 = newdb(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - - let mut db2 = newdb(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - - // make some changes in parallel to db1 and db2.. - let uuid1 = Uuid::new_v4(); - db1.apply(SyncOp::Create { uuid: uuid1 }).unwrap(); - db1.apply(SyncOp::Update { - uuid: uuid1, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - let uuid2 = Uuid::new_v4(); - db2.apply(SyncOp::Create { uuid: uuid2 }).unwrap(); - db2.apply(SyncOp::Update { - uuid: uuid2, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // now make updates to the same task on both sides - db1.apply(SyncOp::Update { - uuid: uuid2, - property: "priority".into(), - value: Some("H".into()), - timestamp: Utc::now(), - }) - .unwrap(); - db2.apply(SyncOp::Update { - uuid: uuid2, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - Ok(()) - } - - #[test] - fn test_sync_create_delete() -> Result<()> { - let mut server: Box = TestServer::new().server(); - - let mut db1 = newdb(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - - let mut db2 = newdb(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - - // create and update a task.. - let uuid = Uuid::new_v4(); - db1.apply(SyncOp::Create { uuid }).unwrap(); - db1.apply(SyncOp::Update { - uuid, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and synchronize those around - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - // delete and re-create the task on db1 - db1.apply(SyncOp::Delete { uuid }).unwrap(); - db1.apply(SyncOp::Create { uuid }).unwrap(); - db1.apply(SyncOp::Update { - uuid, - property: "title".into(), - value: Some("my second task".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - // and on db2, update a property of the task - db2.apply(SyncOp::Update { - uuid, - property: "project".into(), - value: Some("personal".into()), - timestamp: Utc::now(), - }) - .unwrap(); - - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db2.storage.txn()?.as_mut(), false).unwrap(); - sync(&mut server, db1.storage.txn()?.as_mut(), false).unwrap(); - assert_eq!(db1.sorted_tasks(), db2.sorted_tasks()); - - Ok(()) - } - - #[test] - fn test_sync_add_snapshot_start_with_snapshot() -> Result<()> { - let mut test_server = TestServer::new(); - - let mut server: Box = test_server.server(); - let mut db1 = newdb(); - - let uuid = Uuid::new_v4(); - db1.apply(SyncOp::Create { uuid })?; - db1.apply(SyncOp::Update { - uuid, - property: "title".into(), - value: Some("my first task".into()), - timestamp: Utc::now(), - })?; - - test_server.set_snapshot_urgency(SnapshotUrgency::High); - sync(&mut server, db1.storage.txn()?.as_mut(), false)?; - - // assert that a snapshot was added - let base_version = db1.storage.txn()?.base_version()?; - let (v, s) = test_server - .snapshot() - .ok_or_else(|| anyhow::anyhow!("no snapshot"))?; - assert_eq!(v, base_version); - - let tasks = SnapshotTasks::decode(&s)?.into_inner(); - assert_eq!(tasks[0].0, uuid); - - // update the taskdb and sync again - db1.apply(SyncOp::Update { - uuid, - property: "title".into(), - value: Some("my first task, updated".into()), - timestamp: Utc::now(), - })?; - sync(&mut server, db1.storage.txn()?.as_mut(), false)?; - - // delete the first version, so that db2 *must* initialize from - // the snapshot - test_server.delete_version(Uuid::nil()); - - // sync to a new DB and check that we got the expected results - let mut db2 = newdb(); - sync(&mut server, db2.storage.txn()?.as_mut(), false)?; - - let task = db2.get_task(uuid)?.unwrap(); - assert_eq!(task.get("title").unwrap(), "my first task, updated"); - - Ok(()) - } - - #[test] - fn test_sync_avoids_snapshot() -> Result<()> { - let test_server = TestServer::new(); - - let mut server: Box = test_server.server(); - let mut db1 = newdb(); - - let uuid = Uuid::new_v4(); - db1.apply(SyncOp::Create { uuid }).unwrap(); - - test_server.set_snapshot_urgency(SnapshotUrgency::Low); - sync(&mut server, db1.storage.txn()?.as_mut(), true).unwrap(); - - // assert that a snapshot was not added, because we indicated - // we wanted to avoid snapshots and it was only low urgency - assert_eq!(test_server.snapshot(), None); - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/taskdb/undo.rs b/taskchampion/taskchampion/src/taskdb/undo.rs deleted file mode 100644 index c193d96bb..000000000 --- a/taskchampion/taskchampion/src/taskdb/undo.rs +++ /dev/null @@ -1,154 +0,0 @@ -use super::apply; -use crate::errors::Result; -use crate::storage::{ReplicaOp, StorageTxn}; -use log::{debug, info, trace}; - -/// Local operations until the most recent UndoPoint. -pub fn get_undo_ops(txn: &mut dyn StorageTxn) -> Result> { - let mut local_ops = txn.operations().unwrap(); - let mut undo_ops: Vec = Vec::new(); - - while let Some(op) = local_ops.pop() { - if op == ReplicaOp::UndoPoint { - break; - } - undo_ops.push(op); - } - - Ok(undo_ops) -} - -/// Commit operations to storage, returning a boolean indicating success. -pub fn commit_undo_ops(txn: &mut dyn StorageTxn, mut undo_ops: Vec) -> Result { - let mut applied = false; - let mut local_ops = txn.operations().unwrap(); - - // Add UndoPoint to undo_ops unless this undo will empty the operations database, in which case - // there is no UndoPoint. - if local_ops.len() > undo_ops.len() { - undo_ops.push(ReplicaOp::UndoPoint); - } - - // Drop undo_ops iff they're the latest operations. - // TODO Support concurrent undo by adding the reverse of undo_ops rather than popping from operations. - let old_len = local_ops.len(); - let undo_len = undo_ops.len(); - let new_len = old_len - undo_len; - let local_undo_ops = &local_ops[new_len..old_len]; - undo_ops.reverse(); - if local_undo_ops != undo_ops { - info!("Undo failed: concurrent changes to the database occurred."); - debug!( - "local_undo_ops={:#?}\nundo_ops={:#?}", - local_undo_ops, undo_ops - ); - return Ok(applied); - } - undo_ops.reverse(); - local_ops.truncate(new_len); - - for op in undo_ops { - debug!("Reversing operation {:?}", op); - let rev_ops = op.reverse_ops(); - for op in rev_ops { - trace!("Applying reversed operation {:?}", op); - apply::apply_op(txn, &op)?; - applied = true; - } - } - - if undo_len != 0 { - txn.set_operations(local_ops)?; - txn.commit()?; - } - - Ok(applied) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::server::SyncOp; - use crate::taskdb::TaskDb; - use chrono::Utc; - use pretty_assertions::assert_eq; - use uuid::Uuid; - - #[test] - fn test_apply_create() -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - let timestamp = Utc::now(); - - // apply a few ops, capture the DB state, make an undo point, and then apply a few more - // ops. - db.apply(SyncOp::Create { uuid: uuid1 })?; - db.apply(SyncOp::Update { - uuid: uuid1, - property: "prop".into(), - value: Some("v1".into()), - timestamp, - })?; - db.apply(SyncOp::Create { uuid: uuid2 })?; - db.apply(SyncOp::Update { - uuid: uuid2, - property: "prop".into(), - value: Some("v2".into()), - timestamp, - })?; - db.apply(SyncOp::Update { - uuid: uuid2, - property: "prop2".into(), - value: Some("v3".into()), - timestamp, - })?; - - let db_state = db.sorted_tasks(); - - db.add_undo_point()?; - db.apply(SyncOp::Delete { uuid: uuid1 })?; - db.apply(SyncOp::Update { - uuid: uuid2, - property: "prop".into(), - value: None, - timestamp, - })?; - db.apply(SyncOp::Update { - uuid: uuid2, - property: "prop2".into(), - value: Some("new-value".into()), - timestamp, - })?; - - assert_eq!(db.operations().len(), 9, "{:#?}", db.operations()); - - let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?; - assert_eq!(undo_ops.len(), 3, "{:#?}", undo_ops); - - assert!(commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?); - - // Note that we've subtracted the length of undo_ops plus one for the UndoPoint. - assert_eq!(db.operations().len(), 5, "{:#?}", db.operations()); - assert_eq!(db.sorted_tasks(), db_state, "{:#?}", db.sorted_tasks()); - - // Note that the number of undo operations is equal to the number of operations in the - // database here because there are no UndoPoints. - let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?; - assert_eq!(undo_ops.len(), 5, "{:#?}", undo_ops); - - assert!(commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?); - - // empty db - assert_eq!(db.operations().len(), 0, "{:#?}", db.operations()); - assert_eq!(db.sorted_tasks(), vec![], "{:#?}", db.sorted_tasks()); - - let undo_ops = get_undo_ops(db.storage.txn()?.as_mut())?; - assert_eq!(undo_ops.len(), 0, "{:#?}", undo_ops); - - // nothing left to undo, so commit_undo_ops() returns false - assert!(!commit_undo_ops(db.storage.txn()?.as_mut(), undo_ops)?); - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/taskdb/working_set.rs b/taskchampion/taskchampion/src/taskdb/working_set.rs deleted file mode 100644 index c79e488e9..000000000 --- a/taskchampion/taskchampion/src/taskdb/working_set.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::errors::Result; -use crate::storage::{StorageTxn, TaskMap}; -use std::collections::HashSet; - -/// Rebuild the working set using a function to identify tasks that should be in the set. This -/// renumbers the existing working-set tasks to eliminate gaps, and also adds any tasks that -/// are not already in the working set but should be. The rebuild occurs in a single -/// trasnsaction against the storage backend. -pub fn rebuild(txn: &mut dyn StorageTxn, in_working_set: F, renumber: bool) -> Result<()> -where - F: Fn(&TaskMap) -> bool, -{ - let mut new_ws = vec![None]; // index 0 is always None - let mut seen = HashSet::new(); - - // The goal here is for existing working-set items to be "compressed' down to index 1, so - // we begin by scanning the current working set and inserting any tasks that should still - // be in the set into new_ws, implicitly dropping any tasks that are no longer in the - // working set. - for elt in txn.get_working_set()?.drain(1..) { - if let Some(uuid) = elt { - if let Some(task) = txn.get_task(uuid)? { - if in_working_set(&task) { - new_ws.push(Some(uuid)); - seen.insert(uuid); - continue; - } - } - } - - // if we are not renumbering, then insert a blank working-set entry here - if !renumber { - new_ws.push(None); - } - } - - // if renumbering, clear the working set and re-add - if renumber { - txn.clear_working_set()?; - for elt in new_ws.drain(1..new_ws.len()).flatten() { - txn.add_to_working_set(elt)?; - } - } else { - // ..otherwise, just clear the None items determined above from the working set - for (i, elt) in new_ws.iter().enumerate().skip(1) { - if elt.is_none() { - txn.set_working_set_item(i, None)?; - } - } - } - - // Now go hunting for tasks that should be in this list but are not, adding them at the - // end of the list, whether renumbering or not - for (uuid, task) in txn.all_tasks()? { - if !seen.contains(&uuid) && in_working_set(&task) { - txn.add_to_working_set(uuid)?; - } - } - - txn.commit()?; - Ok(()) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::server::SyncOp; - use crate::taskdb::TaskDb; - use chrono::Utc; - use uuid::Uuid; - - #[test] - fn rebuild_working_set_renumber() -> Result<()> { - rebuild_working_set(true) - } - - #[test] - fn rebuild_working_set_no_renumber() -> Result<()> { - rebuild_working_set(false) - } - - fn rebuild_working_set(renumber: bool) -> Result<()> { - let mut db = TaskDb::new_inmemory(); - let mut uuids = vec![]; - uuids.push(Uuid::new_v4()); - println!("uuids[0]: {:?} - pending, not in working set", uuids[0]); - uuids.push(Uuid::new_v4()); - println!("uuids[1]: {:?} - pending, in working set", uuids[1]); - uuids.push(Uuid::new_v4()); - println!("uuids[2]: {:?} - not pending, not in working set", uuids[2]); - uuids.push(Uuid::new_v4()); - println!("uuids[3]: {:?} - not pending, in working set", uuids[3]); - uuids.push(Uuid::new_v4()); - println!("uuids[4]: {:?} - pending, in working set", uuids[4]); - - // add everything to the TaskDb - for uuid in &uuids { - db.apply(SyncOp::Create { uuid: *uuid })?; - } - for i in &[0usize, 1, 4] { - db.apply(SyncOp::Update { - uuid: uuids[*i], - property: String::from("status"), - value: Some("pending".into()), - timestamp: Utc::now(), - })?; - } - - // set the existing working_set as we want it - { - let mut txn = db.storage.txn()?; - txn.clear_working_set()?; - - for i in &[1usize, 3, 4] { - txn.add_to_working_set(uuids[*i])?; - } - - txn.commit()?; - } - - assert_eq!( - db.working_set()?, - vec![None, Some(uuids[1]), Some(uuids[3]), Some(uuids[4])] - ); - - rebuild( - db.storage.txn()?.as_mut(), - |t| { - if let Some(status) = t.get("status") { - status == "pending" - } else { - false - } - }, - renumber, - )?; - - let exp = if renumber { - // uuids[1] and uuids[4] are already in the working set, so are compressed - // to the top, and then uuids[0] is added. - vec![None, Some(uuids[1]), Some(uuids[4]), Some(uuids[0])] - } else { - // uuids[1] and uuids[4] are already in the working set, at indexes 1 and 3, - // and then uuids[0] is added. - vec![None, Some(uuids[1]), None, Some(uuids[4]), Some(uuids[0])] - }; - - assert_eq!(db.working_set()?, exp); - - Ok(()) - } -} diff --git a/taskchampion/taskchampion/src/utils.rs b/taskchampion/taskchampion/src/utils.rs deleted file mode 100644 index 7eb0885dc..000000000 --- a/taskchampion/taskchampion/src/utils.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::convert::TryInto; -use uuid::Uuid; - -/// A representation of a UUID as a key. This is just a newtype wrapping the 128-bit packed form -/// of a UUID. -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct Key(uuid::Bytes); - -impl From<&[u8]> for Key { - fn from(bytes: &[u8]) -> Key { - Key(bytes.try_into().expect("expected 16 bytes")) - } -} - -impl From<&Uuid> for Key { - fn from(uuid: &Uuid) -> Key { - let key = Key(*uuid.as_bytes()); - key - } -} - -impl From for Key { - fn from(uuid: Uuid) -> Key { - let key = Key(*uuid.as_bytes()); - key - } -} - -impl From for Uuid { - fn from(key: Key) -> Uuid { - Uuid::from_bytes(key.0) - } -} - -impl AsRef<[u8]> for Key { - fn as_ref(&self) -> &[u8] { - &self.0[..] - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - #[test] - fn test_from_bytes() { - let k: Key = (&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16][..]).into(); - let u: Uuid = k.into(); - assert_eq!( - u, - Uuid::parse_str("01020304-0506-0708-090a-0b0c0d0e0f10").unwrap() - ); - } - - #[test] - #[should_panic] - fn test_from_bytes_bad_len() { - let _: Key = (&[1u8, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11][..]).into(); - } -} diff --git a/taskchampion/taskchampion/src/workingset.rs b/taskchampion/taskchampion/src/workingset.rs deleted file mode 100644 index 15a509753..000000000 --- a/taskchampion/taskchampion/src/workingset.rs +++ /dev/null @@ -1,154 +0,0 @@ -use std::collections::HashMap; -use uuid::Uuid; - -/// A WorkingSet represents a snapshot of the working set from a replica. -/// -/// A replica's working set is a mapping from small integers to task uuids for all pending tasks. -/// The small integers are meant to be stable, easily-typed identifiers for users to interact with -/// important tasks. -/// -/// IMPORTANT: the content of the working set may change at any time that a DB transaction is not -/// in progress, and the data in this type will not be updated automatically. It is up to the -/// caller to decide how long to keep this value, and how much to trust the accuracy of its -/// contents. In practice, the answers are usually "a few milliseconds" and treating unexpected -/// results as non-fatal. -pub struct WorkingSet { - by_index: Vec>, - by_uuid: HashMap, -} - -impl WorkingSet { - /// Create a new WorkingSet. Typically this is acquired via `replica.working_set()` - pub(crate) fn new(by_index: Vec>) -> Self { - let mut by_uuid = HashMap::new(); - - // working sets are 1-indexed, so element 0 should always be None - assert!(by_index.is_empty() || by_index[0].is_none()); - - for (index, uuid) in by_index.iter().enumerate() { - if let Some(uuid) = uuid { - by_uuid.insert(*uuid, index); - } - } - Self { by_index, by_uuid } - } - - /// Get the "length" of the working set: the total number of uuids in the set. - pub fn len(&self) -> usize { - self.by_index.iter().filter(|e| e.is_some()).count() - } - - /// Get the largest index in the working set, or zero if the set is empty. - pub fn largest_index(&self) -> usize { - self.by_index.len().saturating_sub(1) - } - - /// True if the length is zero - pub fn is_empty(&self) -> bool { - self.by_index.iter().all(|e| e.is_none()) - } - - /// Get the uuid with the given index, if any exists. - pub fn by_index(&self, index: usize) -> Option { - if let Some(Some(uuid)) = self.by_index.get(index) { - Some(*uuid) - } else { - None - } - } - - /// Get the index for the given uuid, if any - pub fn by_uuid(&self, uuid: Uuid) -> Option { - self.by_uuid.get(&uuid).copied() - } - - /// Iterate over pairs (index, uuid), in order by index. - pub fn iter(&self) -> impl Iterator + '_ { - self.by_index - .iter() - .enumerate() - .filter_map(|(index, uuid)| uuid.as_ref().map(|uuid| (index, *uuid))) - } -} - -#[cfg(test)] -mod test { - use super::*; - use pretty_assertions::assert_eq; - - fn make() -> (Uuid, Uuid, WorkingSet) { - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - ( - uuid1, - uuid2, - WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]), - ) - } - - #[test] - fn test_new() { - let (_, uuid2, ws) = make(); - assert_eq!(ws.by_index[3], Some(uuid2)); - assert_eq!(ws.by_uuid.get(&uuid2), Some(&3)); - } - - #[test] - fn test_len_and_is_empty() { - let (_, _, ws) = make(); - assert_eq!(ws.len(), 2); - assert_eq!(ws.is_empty(), false); - - let ws = WorkingSet::new(vec![]); - assert_eq!(ws.len(), 0); - assert_eq!(ws.is_empty(), true); - - let ws = WorkingSet::new(vec![None, None, None]); - assert_eq!(ws.len(), 0); - assert_eq!(ws.is_empty(), true); - } - - #[test] - fn test_largest_index() { - let uuid1 = Uuid::new_v4(); - let uuid2 = Uuid::new_v4(); - - let ws = WorkingSet::new(vec![]); - assert_eq!(ws.largest_index(), 0); - - let ws = WorkingSet::new(vec![None, Some(uuid1)]); - assert_eq!(ws.largest_index(), 1); - - let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2)]); - assert_eq!(ws.largest_index(), 3); - - let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]); - assert_eq!(ws.largest_index(), 4); - } - - #[test] - fn test_by_index() { - let (uuid1, uuid2, ws) = make(); - assert_eq!(ws.by_index(0), None); - assert_eq!(ws.by_index(1), Some(uuid1)); - assert_eq!(ws.by_index(2), None); - assert_eq!(ws.by_index(3), Some(uuid2)); - assert_eq!(ws.by_index(4), None); - assert_eq!(ws.by_index(100), None); // past the end of the vector - } - - #[test] - fn test_by_uuid() { - let (uuid1, uuid2, ws) = make(); - let nosuch = Uuid::new_v4(); - assert_eq!(ws.by_uuid(uuid1), Some(1)); - assert_eq!(ws.by_uuid(uuid2), Some(3)); - assert_eq!(ws.by_uuid(nosuch), None); - } - - #[test] - fn test_iter() { - let (uuid1, uuid2, ws) = make(); - assert_eq!(ws.iter().collect::>(), vec![(1, uuid1), (3, uuid2),]); - } -} diff --git a/taskchampion/xtask/Cargo.toml b/taskchampion/xtask/Cargo.toml deleted file mode 100644 index ad3d76837..000000000 --- a/taskchampion/xtask/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "xtask" -version = "0.4.1" -edition = "2021" - -[dependencies] -anyhow.workspace = true -taskchampion-lib = { path = "../lib" } -regex = "^1.10.2" diff --git a/taskchampion/xtask/src/main.rs b/taskchampion/xtask/src/main.rs deleted file mode 100644 index 446787db6..000000000 --- a/taskchampion/xtask/src/main.rs +++ /dev/null @@ -1,124 +0,0 @@ -//! This executable defines the `cargo xtask` subcommands. -//! -//! At the moment it is very simple, but if this grows more subcommands then -//! it will be sensible to use `clap` or another similar library. - -use regex::Regex; -use std::env; -use std::fs::File; -use std::io::{BufRead, BufReader, Seek, Write}; -use std::path::{Path, PathBuf}; - -/// Tuples of the form (PATH, REGEX) where PATH and REGEX are literals where PATH is a file that -/// conains the Minimum Supported Rust Version and REGEX is the pattern to find the appropriate -/// line in the file. PATH is relative to the `taskchampion/` directory in the repo. -const MSRV_PATH_REGEX: &[(&str, &str)] = &[ - ( - "../.github/workflows/checks.yml", - r#"toolchain: "[0-9.]+*" # MSRV"#, - ), - ("../.github/workflows/rust-tests.yml", r#""[0-9.]+" # MSRV"#), - ( - "taskchampion/src/lib.rs", - r#"Rust version [0-9.]* and higher"#, - ), - ("taskchampion/Cargo.toml", r#"^rust-version = "[0-9.]"#), -]; - -pub fn main() -> anyhow::Result<()> { - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?); - let workspace_dir = manifest_dir.parent().unwrap(); - let arguments: Vec = env::args().collect(); - - if arguments.len() < 2 { - anyhow::bail!("xtask: Valid arguments are: `codegen`, `msrv `"); - } - - match arguments[1].as_str() { - "codegen" => codegen(workspace_dir), - "msrv" => msrv(arguments, workspace_dir), - _ => anyhow::bail!("xtask: unknown xtask"), - } -} - -/// `cargo xtask codegen` -/// -/// This uses ffizz-header to generate `lib/taskchampion.h`. -fn codegen(workspace_dir: &Path) -> anyhow::Result<()> { - let lib_crate_dir = workspace_dir.join("lib"); - let mut file = File::create(lib_crate_dir.join("taskchampion.h")).unwrap(); - write!(&mut file, "{}", ::taskchampion_lib::generate_header()).unwrap(); - - Ok(()) -} - -/// `cargo xtask msrv (X.Y)` -/// -/// This checks and updates the Minimum Supported Rust Version for all files specified in MSRV_PATH_REGEX`. -/// Each line where the regex matches will have all values of the form `#.##` replaced with the given MSRV. -fn msrv(args: Vec, workspace_dir: &Path) -> anyhow::Result<()> { - // check that (X.Y) argument is (mostly) valid: - if args.len() < 3 || !args[2].chars().all(|c| c.is_numeric() || c == '.') { - anyhow::bail!("xtask: Invalid argument format. Xtask msrv argument takes the form \"X.Y(y)\", where XYy are numbers. eg: `cargo run xtask msrv 1.68`"); - } - let version_replacement_string = &args[2]; - - // set regex for replacing version number only within the pattern found within a line - let re_msrv_version = Regex::new(r"([0-9]+(\.|[0-9]+|))+")?; - - // for each file in const paths tuple - for msrv_file in MSRV_PATH_REGEX { - let mut is_pattern_in_file = false; - - let path = workspace_dir.join(msrv_file.0); - let path = Path::new(&path); - if !path.exists() { - anyhow::bail!("xtask: path does not exist {}", &path.display()); - }; - - let mut file = File::options().read(true).write(true).open(path)?; - let reader = BufReader::new(&file); - - // set search string and the replacement string for version number content - let re_msrv_pattern = Regex::new(msrv_file.1)?; - - // for each line in file - let mut file_string = String::new(); - for line in reader.lines() { - let line = &line?; - - // if rust version pattern is found and is different, update it - if let Some(pattern_offset) = re_msrv_pattern.find(line) { - if !pattern_offset.as_str().contains(version_replacement_string) { - file_string += &re_msrv_version.replace(line, version_replacement_string); - - file_string += "\n"; - - is_pattern_in_file = true; - continue; - } - } - - file_string += line; - file_string += "\n"; - } - - // if pattern was found and updated, write to disk - if is_pattern_in_file { - // Set the file length to the file_string length - file.set_len(file_string.len() as u64)?; - - // set the cursor to the beginning of the file and write - file.seek(std::io::SeekFrom::Start(0))?; - file.write_all(file_string.as_bytes())?; - - // notify user this file was updated - println!( - "xtask: Updated MSRV in {}", - re_msrv_version.replace(msrv_file.0, version_replacement_string) - ); - } - } - - Ok(()) -} diff --git a/test/.gitignore b/test/.gitignore deleted file mode 100644 index cdb132ea3..000000000 --- a/test/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -*.o -*.pyc -*.data -*.sqlite3 -*.log -*.runlog -col.t -dom.t -eval.t -lexer.t -t.t -taskmod.t -tdb2.t -uri.t -util.t -variant_add.t -variant_and.t -variant_cast.t -variant_divide.t -variant_equal.t -variant_exp.t -variant_gt.t -variant_gte.t -variant_inequal.t -variant_lt.t -variant_lte.t -variant_match.t -variant_math.t -variant_modulo.t -variant_multiply.t -variant_nomatch.t -variant_not.t -variant_or.t -variant_partial.t -variant_subtract.t -variant_xor.t -view.t -tc.t -tw-2689.t - -json_test diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 482014895..acff66c67 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,44 +1,256 @@ cmake_minimum_required (VERSION 3.22) -# This is a work-around for the following CMake issue: -# https://gitlab.kitware.com/cmake/cmake/issues/16062 -# The issue has been fixed in CMake 3.11.0; the policy is set -# to OLD for compatibility with older versions of CMake only. -if(POLICY CMP0037 AND ${CMAKE_VERSION} VERSION_LESS "3.11.0") - cmake_policy(SET CMP0037 OLD) -endif() +# -- C++ tests include_directories (${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/commands ${CMAKE_SOURCE_DIR}/src/columns ${CMAKE_SOURCE_DIR}/src/libshared/src - ${CMAKE_SOURCE_DIR}/src/taskchampion/lib ${CMAKE_SOURCE_DIR}/test - ${CMAKE_SOURCE_DIR}/taskchampion/lib ${TASK_INCLUDE_DIRS}) -set (test_SRCS col.t dom.t eval.t lexer.t t.t tw-2689.t tdb2.t tc.t util.t variant_add.t variant_and.t variant_cast.t variant_divide.t variant_equal.t variant_exp.t variant_gt.t variant_gte.t variant_inequal.t variant_lt.t variant_lte.t variant_match.t variant_math.t variant_modulo.t variant_multiply.t variant_nomatch.t variant_not.t variant_or.t variant_partial.t variant_subtract.t variant_xor.t view.t) +# All C++ test files. Note that the portion before `.cpp` must be a valid, +# unique C++ identifier. +set(test_SRCS + col_test.cpp + dom_test.cpp + eval_test.cpp + lexer_test.cpp + t_test.cpp + tw_2689_test.cpp + tdb2_test.cpp + tc_cpp_test.cpp + util_test.cpp + variant_add_test.cpp + variant_and_test.cpp + variant_cast_test.cpp + variant_divide_test.cpp + variant_equal_test.cpp + variant_exp_test.cpp + variant_gt_test.cpp + variant_gte_test.cpp + variant_inequal_test.cpp + variant_lt_test.cpp + variant_lte_test.cpp + variant_match_test.cpp + variant_math_test.cpp + variant_modulo_test.cpp + variant_multiply_test.cpp + variant_nomatch_test.cpp + variant_not_test.cpp + variant_or_test.cpp + variant_partial_test.cpp + variant_subtract_test.cpp + variant_xor_test.cpp + view_test.cpp + ) -add_custom_target (test ./run_all --verbose - DEPENDS ${test_SRCS} task_executable - WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test) +# Build `test_runner` containing all CPP tests, linked once. +create_test_sourcelist (cpp_test_SRCS cpp_tests.cpp ${test_SRCS}) +add_executable(test_runner + test.cpp + ${cpp_test_SRCS} +) +target_link_libraries (test_runner task commands columns libshared task commands columns libshared task commands columns libshared ${TASK_LIBRARIES}) +if (DARWIN) + target_link_libraries (test_runner "-framework CoreFoundation -framework Security -framework SystemConfiguration") +endif (DARWIN) -add_custom_target (build_tests DEPENDS ${test_SRCS} - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test) +foreach (test_FILE ${test_SRCS}) + get_filename_component (test_NAME ${test_FILE} NAME_WE) + # Tell the source file what its own name is + set_source_files_properties(${test_FILE} PROPERTIES COMPILE_FLAGS -DTEST_NAME=${test_NAME}) + add_test(NAME ${test_FILE} + COMMAND test_runner ${test_NAME} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach (test_FILE) -foreach (src_FILE ${test_SRCS}) - add_executable (${src_FILE} "${src_FILE}.cpp" test.cpp) - target_link_libraries (${src_FILE} task tc commands columns libshared task tc commands columns libshared task commands columns libshared ${TASK_LIBRARIES}) - if (DARWIN) - target_link_libraries (${src_FILE} "-framework CoreFoundation -framework Security -framework SystemConfiguration") - endif (DARWIN) -endforeach (src_FILE) +# -- Python tests -configure_file(run_all run_all COPYONLY) -configure_file(problems problems COPYONLY) +add_subdirectory(basetest) +add_subdirectory(simpletap) -#SET(CMAKE_BUILD_TYPE gcov) -#SET(CMAKE_CXX_FLAGS_GCOV "--coverage") -#SET(CMAKE_C_FLAGS_GCOV "--coverage") -#SET(CMAKE_EXE_LINKER_FLAGS_GCOV "--coverage") +set (pythonTests + abbreviation.test.py + add.test.py + alias.test.py + annotate.test.py + append.test.py + args.test.py + bash_completion.test.py + blocked.test.py + bulk.test.py + burndown.test.py + calc.test.py + calendar.test.py + caseless.test.py + color.cmd.test.py + color.rules.test.py + columns.test.py + commands.test.py + completed.test.py + configuration.test.py + confirmation.test.py + context.test.py + count.test.py + custom.config.test.py + custom.recur_ind.test.py + custom.test.py + custom.tag_ind.test.py + date.iso.test.py + dateformat.test.py + datesort.test.py + datetime-negative.test.py + debug.test.py + default.test.py + delete.test.py + denotate.test.py + dependencies.test.py + diag.test.py + diag_color.test.py + dom2.test.py + due.test.py + duplicate.test.py + edit.test.py + encoding.test.py + enpassant.test.py + exec.test.py + export.test.py + feature.559.test.py + feature.default.project.test.py + feature.print.empty.columns.test.py + feature.recurrence.test.py + feedback.test.py + filter.test.py + fontunderline.test.py + format.test.py + gc.test.py + helpers.test.py + history.test.py + hooks.env.test.py + hooks.on-add.test.py + hooks.on-launch.test.py + hooks.on-modify.test.py + hyphenate.test.py + ids.test.py + import.test.py + import-v2.test.py + info.test.py + limit.test.py + list.all.projects.test.py + log.test.py + logo.test.py + math.test.py + modify.test.py + nag.test.py + news.test.py + obfuscate.test.py + oldest.test.py + operators.test.py + overdue.test.py + partial.test.py + prepend.test.py + pri_sort.test.py + project.test.py + purge.test.py + quotes.test.py + rc.override.test.py + read-only.test.py + recurrence.test.py + reports.test.py + search.test.py + sequence.test.py + shell.test.py + show.test.py + sorting.test.py + special.test.py + start.test.py + stats.test.py + substitute.test.py + sugar.test.py + summary.test.py + tag.test.py + taskrc.test.py + timesheet.test.py + tw-1379.test.py + tw-1837.test.py + tw-1999.test.py + tw-20.test.py + tw-2575.test.py + tw-262.test.py + tw-295.test.py + tw-3527.test.py + uda.test.py + uda_orphan.test.py + uda_report.test.py + uda_sort.test.py + undo.test.py + unicode.test.py + unique.test.py + unusual_task.test.py + upgrade.test.py + urgency.test.py + urgency_inherit.test.py + uuid.test.py + verbose.test.py + version.test.py + wait.test.py + hooks.on-exit.test.py + ) + +foreach (python_Test ${pythonTests}) + configure_file(${python_Test} ${python_Test} COPYONLY) + + add_test(NAME ${python_Test} + COMMAND ${Python_EXECUTABLE} ${python_Test} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach(python_Test) + +# Create a `make_tc_task` binary, used for unusual_task.test.py. In order to build this +# for the tests, it's added as a dependency of `test_runner`. +add_executable (make_tc_task make_tc_task.cpp) +target_link_libraries (make_tc_task task commands columns libshared task commands columns libshared task commands columns libshared ${TASK_LIBRARIES}) +if (DARWIN) + target_link_libraries (make_tc_task "-framework CoreFoundation -framework Security -framework SystemConfiguration") +endif (DARWIN) +add_dependencies(test_runner make_tc_task) + +# -- Shell tests + +set (shell_SRCS + tw-1637.test.sh + tw-1643.test.sh + tw-1688.test.sh + tw-1715.test.sh + tw-1718.test.sh + tw-1804.test.sh + tw-1883.test.sh + tw-1895.test.sh + tw-1938.test.sh + tw-2124.test.sh + tw-2189.test.sh + tw-2257.test.sh + tw-2386.test.sh + tw-2392.test.sh + tw-2429.test.sh + tw-2451.test.sh + tw-2514.test.sh + tw-2530.test.sh + tw-2550.test.sh + tw-2581.test.sh + tw-3102.test.sh + tw-3109.test.sh +) + +configure_file(bash_tap.sh bash_tap.sh COPYONLY) +configure_file(bash_tap_tw.sh bash_tap_tw.sh COPYONLY) + +foreach (shell_Test ${shell_SRCS}) + add_test(NAME ${shell_Test} + COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/${shell_Test} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + ) +endforeach(shell_Test) diff --git a/test/README b/test/README.md similarity index 69% rename from test/README rename to test/README.md index c6334c8ee..e00621ffc 100644 --- a/test/README +++ b/test/README.md @@ -1,36 +1,18 @@ -README -====== - -This is the task.git/test/README file, and contains notes about the Taskwarrior -test suite. - - -Running Tests -------------- - +## Running Tests Do this to run all tests: +```shell +cmake --build build --target test_runner --target task_executable +ctest --test-dir build +``` - $ cd test && make && ./run_all && ./problems +All tests produce TAP (Test Anything Protocol) output. +In order to run the tests in parallel add the `--parallel <# of threads>` or shortly `-j <# of threads>` option to `ctest`. +Depending on your IDE, all tests might also be available under the `All CTest` target. +Keep in mind that the tests are not automatically rebuild if a source file is changes, it requires a manual rebuild. -All unit tests produce TAP (Test Anything Protocol) output, and are run by the -'run_all' test harness. +Please also have a look at [development.md](../doc/devel/contrib/development.md) for more information on how to run tests as well as some information about `ctest`. -The 'run_all' script produces an 'all.log' file which is the accumulated output -of all tests. Before executing 'run_all' you need to compile the C++ unit -tests, by running 'make' in the 'test' directory. - -The script 'problems' will list all the tests that fail, with a count of the -failing tests, once you have run all the tests and produced an 'all.log' file. - -Any TAP harness may be used. - -Note that adding the '--serial' option to ./run_all, all tests are executed -serially. The default runs Python, C++ and Bash tests in parallel. Using -'--serial' will make for a slower test run. - - -Architecture ------------- +## Architecture There are three varieties of tests: @@ -38,23 +20,25 @@ There are three varieties of tests: very fast tests, and are exhaustive in nature. * Python unit tests that are at the highest level, exercising the command - line, hooks and syncing. There is an example, 'template.t', that shows how - to perform various high level tests. + line, hooks and syncing. There is an example, 'template.test.py', that + shows how to perform various high level tests. * Bash unit tests, one test per file, using the bash_tap_tw.sh script. These tests are small, quick tests, not intended to be permanent. -All tests are named with the pattern '*.t', and any other forms are not run by -the test harness. Additionally a test must be set executable (chmod +x) for it -to be run. In the case of Python tests one can still run them manually by -launching them with 'python test.t' or simply './test.t'. It also allows us to -keep tests submitted for bugs that are not scheduled to be fixed in the -upcoming release, and we don't want the failing tests to prevent us from seeing -100% pass rate for the bugs we *have* fixed. +All tests are named with the pattern '*.test.py', '*.test.sh', or '*.test.cpp', +and any other forms are not run by the test harness. +In the case of Python tests one can still run them manually by launching them with 'python testname.test.py' or simply './testname.test.py'. + +If a test is failing and can not be fixed, it can be marked as `WILL_FAIL` in the `CMakeLists.txt` file. +See the [WILL_FAIL](https://cmake.org/cmake/help/latest/prop_test/WILL_FAIL.html) documentation for more information. +However, please keep in mind that such tests should be fixed as soon as possible as well as proper documentation should be added to the issue tracker. + +It also allows us to keep tests submitted for bugs that are not scheduled to be fixed in the upcoming release, and we don't want +the failing tests to prevent us from seeing 100% pass rate for the bugs we *have* fixed. -Goals ------ +## Goals The test suite is evolving, and becoming a better tool for determining whether code is ready for release. There are goals that shape these changes, and they @@ -70,16 +54,14 @@ are: There is simply no point in testing a feature twice, in the same manner. -What Makes a Good Test ----------------------- +## What Makes a Good Test A good test ensures that a feature is functioning as expected, and contains both positive and negative aspects, or in other words looks for expected behavior as well as looking for the absence of unexpected behavior. -Conventions for writing a test ------------------------------- +## Conventions for writing a test If you wish to contribute tests, please consider the following guidelines: @@ -108,14 +90,12 @@ If you wish to contribute tests, please consider the following guidelines: a live test that is skipped, than no test. -How to Submit a Test Change/Addition ------------------------------------- +## How to Submit a Test Change/Addition Mail it to support@gothenburgbitfactory.org, or attach it to an open bug. -Wisdom ------- +## Wisdom Here are some guildelines that may help: @@ -145,8 +125,7 @@ Here are some guildelines that may help: are reported. -TODO ----- +## TODO For anyone looking for test-related tasks to take on, here are some suggestions: @@ -161,5 +140,3 @@ For anyone looking for test-related tasks to take on, here are some suggestions: * All the attribute modifiers need to be tested, only a few are. * Aliases are not well tested, and fragile. - ---- diff --git a/test/abbreviation.t b/test/abbreviation.test.py similarity index 96% rename from test/abbreviation.t rename to test/abbreviation.test.py index c3acf4fd6..94b0c4577 100755 --- a/test/abbreviation.t +++ b/test/abbreviation.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -89,10 +89,11 @@ class TestAbbreviation(TestCase): class TestBug1006(TestCase): """Bug with expansion of abbreviation "des" in task descriptions and annotations. - It happens for all the shortcuts for column attributes that are automatically - completed. This is because DOM elements are checked before standard words - when strings are tokenized. + It happens for all the shortcuts for column attributes that are automatically + completed. This is because DOM elements are checked before standard words + when strings are tokenized. """ + def setUp(self): self.t = Task() self.t.config("verbose", "affected") @@ -159,6 +160,7 @@ class TestBug1687(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/add.t b/test/add.test.py similarity index 90% rename from test/add.t rename to test/add.test.py index e83fe5103..ac635c03d 100755 --- a/test/add.t +++ b/test/add.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -59,11 +59,11 @@ class TestAdd(TestCase): def test_floating_point_preservation(self): """924: Verify that floating point numbers are unmolested - Bug 924: '1.0' --> '1.0000' + Bug 924: '1.0' --> '1.0000' """ self.t("add release 1.0") self.t("add 'release 2.0'") - self.t("add \\\"release 3.0\\\"") + self.t('add \\"release 3.0\\"') code, out, err = self.t("_get 1.description") self.assertEqual(out, "release 1.0\n") @@ -77,19 +77,19 @@ class TestAdd(TestCase): def test_escaped_quotes_are_preserved(self): """917: Verify that escaped quotes are preserved - Bug 917: escaping runs amok + Bug 917: escaping runs amok """ self.t("add one \\'two\\' three") - self.t("add four \\\"five\\\" six") + self.t('add four \\"five\\" six') code, out, err = self.t("list") self.assertIn("one 'two' three", out) - self.assertIn("four \"five\" six", out) + self.assertIn('four "five" six', out) def test_extra_space_in_path(self): """884: Test that path-like args are preserved - Bug 884: Extra space in path name. + Bug 884: Extra space in path name. """ self.t("add /one/two/three/") self.t("add '/four/five/six/'") @@ -101,9 +101,9 @@ class TestAdd(TestCase): def test_parentheses_and_spaces_preserved(self): """819: Test parentheses and spacing is preserved on add - Bug 819: When I run "task add foo\'s bar." the description of the new task is "foo 's bar .". + Bug 819: When I run "task add foo\'s bar." the description of the new task is "foo 's bar .". """ - self.t("add foo\\\'s bar") + self.t("add foo\\'s bar") self.t("add foo (bar)") self.t("add 'baz (qux)'") @@ -115,11 +115,11 @@ class TestAdd(TestCase): def test_single_quote_preserved(self): """1642: Test single quote in a terminated multi-word string is preserved - TW-1642: After "--", an apostrophe unexpectedly ends the task description + TW-1642: After "--", an apostrophe unexpectedly ends the task description """ - self.t("add -- \"Return Randy's stuff\"") + self.t('add -- "Return Randy\'s stuff"') - code, out, err = self.t ("_get 1.description") + code, out, err = self.t("_get 1.description") self.assertIn("Return Randy's stuff\n", out) @@ -191,7 +191,7 @@ class Test1549(TestCase): """ # This command will hang and therefore timeout in 2.4.1. - code, out, err = self.t('rc.verbose:new-id add 1e x') + code, out, err = self.t("rc.verbose:new-id add 1e x") self.assertIn("Created task 1.", out) @@ -203,7 +203,7 @@ class TestBug1612(TestCase): def test_spurious_whitespace(self): """1612: ensure that extra whitespace does not get added. - tw-1612: Spurious whitespace added in task descriptions around certain symbols + tw-1612: Spurious whitespace added in task descriptions around certain symbols """ self.t("add 'foo-bar (http://baz.org/)'") self.t("add 'spam (foo bar)'") @@ -233,8 +233,8 @@ class TestBug1719(TestCase): def test_improper_ordinals(self): """1719: Description cannot contain improper ordinals""" - self.t("add one 1th"); - self.t("add two 23rd"); + self.t("add one 1th") + self.t("add two 23rd") code, out, err = self.t("_get 1.description") self.assertEqual("one 1th\n", out) @@ -245,6 +245,7 @@ class TestBug1719(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/alias.t b/test/alias.test.py similarity index 78% rename from test/alias.t rename to test/alias.test.py index 544ed3801..f7cb4f918 100755 --- a/test/alias.t +++ b/test/alias.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest from datetime import datetime + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -56,28 +56,25 @@ class TestAlias(TestCase): # Sanity check that _projects command outputs the "Home" project code, out, err = self.t("_projects") - self.assertIn(expected, out, - msg="task _projects -> Home") + self.assertIn(expected, out, msg="task _projects -> Home") # Check that foo command outputs the "Home" project code, out, err = self.t("foo") - self.assertIn(expected, out, - msg="task foo -> _projects > Home") + self.assertIn(expected, out, msg="task foo -> _projects > Home") # Check that bar command outputs the "Home" project code, out, err = self.t("bar") - self.assertIn(expected, out, - msg="task bar -> foo > _projects > Home") + self.assertIn(expected, out, msg="task bar -> foo > _projects > Home") # Check that baz command outputs the "Home" project code, out, err = self.t("baz") - self.assertIn(expected, out, - msg="task baz -> bar > foo > _projects > Home") + self.assertIn(expected, out, msg="task baz -> bar > foo > _projects > Home") # Check that qux command outputs the "Home" project code, out, err = self.t("qux") - self.assertIn(expected, out, - msg="task qux -> baz > bar > foo > _projects > Home") + self.assertIn( + expected, out, msg="task qux -> baz > bar > foo > _projects > Home" + ) def test_alias_with_implicit_filter(self): """Test alias containing simple filter string""" @@ -92,17 +89,17 @@ class TestAlias(TestCase): # Sanity check that _projects command outputs # both the "Home" and "Work" projects code, out, err = self.t("_projects") - self.assertIn("Home", out, - msg="task _projects -> Home") - self.assertIn("Work", out, - msg="task _projects -> Work") + self.assertIn("Home", out, msg="task _projects -> Home") + self.assertIn("Work", out, msg="task _projects -> Work") # Check that foo command outputs the "Home" project code, out, err = self.t("foofilter") - self.assertIn("Home", out, - msg="task foofilter -> project:Home _projects > Home") - self.assertNotIn("Work", out, - msg="task foofilter -> project:Home _projects > Work") + self.assertIn( + "Home", out, msg="task foofilter -> project:Home _projects > Home" + ) + self.assertNotIn( + "Work", out, msg="task foofilter -> project:Home _projects > Work" + ) def test_alias_with_implicit_complex_filter(self): """Test alias containing filter string with conjuction""" @@ -117,20 +114,28 @@ class TestAlias(TestCase): # Check that hometoday command outputs the "Home urgent task" code, out, err = self.t("hometoday") - self.assertIn("Home urgent task", out, - msg="task hometoday -> project:Home and due:today minimal > " - "Home urgent task") + self.assertIn( + "Home urgent task", + out, + msg="task hometoday -> project:Home and due:today minimal > " + "Home urgent task", + ) # It should not output "Home task", as that one is not due:today - self.assertNotIn("Home task", out, - msg="task hometoday -> project:Home and due:today minimal > " - "Home task") + self.assertNotIn( + "Home task", + out, + msg="task hometoday -> project:Home and due:today minimal > " "Home task", + ) # It should not output "Work task" either, it has entirely wrong # project - self.assertNotIn("Work task", out, - msg="task hometoday -> project:Home and due:today minimal > " - "Work task") + self.assertNotIn( + "Work task", + out, + msg="task hometoday -> project:Home and due:today minimal > " "Work task", + ) + class TestAliasesCommand(TestCase): def setUp(self): @@ -143,6 +148,7 @@ class TestAliasesCommand(TestCase): code, out, err = self.t("_aliases") self.assertIn("foo", out) + class TestBug1652(TestCase): def setUp(self): """Executed before each test in the class""" @@ -157,6 +163,7 @@ class TestBug1652(TestCase): self.assertIn("Deleted 1 task.", out) self.assertNotIn("No matches.", err) + class TestBug1031(TestCase): def setUp(self): """Executed before each test in the class""" @@ -171,7 +178,7 @@ class TestBug1031(TestCase): self.t("add from") code, out, err = self.t("1 info") - expected = "Description\s+to" + expected = r"Description\s+to" self.assertRegex(out, expected) def test_alias_to_to(self): @@ -179,7 +186,7 @@ class TestBug1031(TestCase): self.t("add from -- to") code, out, err = self.t("1 info") - expected = "Description\s+to to" + expected = r"Description\s+to to" self.assertRegex(out, expected) def test_alias_to_from(self): @@ -187,7 +194,7 @@ class TestBug1031(TestCase): self.t("add to -- from") code, out, err = self.t("1 info") - expected = "Description\s+to from" + expected = r"Description\s+to from" self.assertRegex(out, expected) @@ -197,23 +204,24 @@ class Test1445(TestCase): def test_alias_single_word(self): """1445: Verify single-word aliases""" - self.t.config('alias.when', 'execute date') - code, out, err = self.t('when') + self.t.config("alias.when", "execute date") + code, out, err = self.t("when") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) self.assertIn(str(datetime.now().year), out) def test_alias_multi_word(self): """1445: Verify multi-word aliases""" - self.t.config('alias.worktasks', 'list +work') - self.t('add one +work') - self.t('add two') - code, out, err = self.t('worktasks') + self.t.config("alias.worktasks", "list +work") + self.t("add one +work") + self.t("add two") + code, out, err = self.t("worktasks") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('one', out) + self.assertIn("one", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/annotate.t b/test/annotate.test.py similarity index 72% rename from test/annotate.t rename to test/annotate.test.py index c4fbd6caf..210095826 100755 --- a/test/annotate.t +++ b/test/annotate.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -78,27 +78,45 @@ class TestAnnotate(TestCase): # NOTE: Use 'rrr' to guarantee a unique report name. Using 'r' # conflicts with 'recurring'. self.t.config("report.rrr.description", "rrr") - self.t.config("report.rrr.columns", "id,description") - self.t.config("report.rrr.sort", "id+") - self.t.config("dateformat", "m/d/Y") - self.t.config("color", "0") + self.t.config("report.rrr.columns", "id,description") + self.t.config("report.rrr.sort", "id+") + self.t.config("dateformat", "m/d/Y") + self.t.config("color", "0") code, out, err = self.t("rrr") self.assertTasksExist(out) - self.assertRegex(out, "one\n.+\d{1,2}/\d{1,2}/\d{4}\s+foo1", - msg='full - first annotation task 1') - self.assertRegex(out, "foo1\n.+\d{1,2}/\d{1,2}/\d{4}\s+foo2", - msg='full - first annotation task 1') - self.assertRegex(out, "foo2\n.+\d{1,2}/\d{1,2}/\d{4}\s+foo3", - msg='full - first annotation task 1') - self.assertRegex(out, "two\n.+\d{1,2}/\d{1,2}/\d{4}\s+bar1", - msg='full - first annotation task 1') - self.assertRegex(out, "bar1\n.+\d{1,2}/\d{1,2}/\d{4}\s+bar2", - msg='full - first annotation task 1') - self.assertRegex(out, "three\n.+\d{1,2}/\d{1,2}/\d{4}\s+baz1", - msg='full - first annotation task 1') + self.assertRegex( + out, + "one\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+foo1", + msg="full - first annotation task 1", + ) + self.assertRegex( + out, + "foo1\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+foo2", + msg="full - first annotation task 1", + ) + self.assertRegex( + out, + "foo2\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+foo3", + msg="full - first annotation task 1", + ) + self.assertRegex( + out, + "two\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+bar1", + msg="full - first annotation task 1", + ) + self.assertRegex( + out, + "bar1\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+bar2", + msg="full - first annotation task 1", + ) + self.assertRegex( + out, + "three\n.+\\d{1,2}/\\d{1,2}/\\d{4}\\s+baz1", + msg="full - first annotation task 1", + ) def test_annotate_dateformat(self): """Testing annotations in reports using dateformat.annotation""" @@ -106,26 +124,45 @@ class TestAnnotate(TestCase): # NOTE: Use 'rrr' to guarantee a unique report name. Using 'r' # conflicts with 'recurring'. self.t.config("report.rrr.description", "rrr") - self.t.config("report.rrr.columns", "id,description") - self.t.config("report.rrr.sort", "id+") - self.t.config("dateformat.annotation", "yMD HNS") + self.t.config("report.rrr.columns", "id,description") + self.t.config("report.rrr.sort", "id+") + self.t.config("dateformat.annotation", "yMD HNS") code, out, err = self.t("rrr") self.assertTasksExist(out) - self.assertRegex(out, "one\n.+\d{1,6}\s+\d{1,6}\s+foo1", - msg="dateformat - first annotation task 1") - self.assertRegex(out, "foo1\n.+\d{1,6}\s+\d{1,6}\s+foo2", - msg="dateformat - second annotation task 1") - self.assertRegex(out, "foo2\n.+\d{1,6}\s+\d{1,6}\s+foo3", - msg="dateformat - third annotation task 1") - self.assertRegex(out, "two\n.+\d{1,6}\s+\d{1,6}\s+bar1", - msg="dateformat - first annotation task 2") - self.assertRegex(out, "bar1\n.+\d{1,6}\s+\d{1,6}\s+bar2", - msg="dateformat - second annotation task 2") - self.assertRegex(out, "three\n.+\d{1,6}\s+\d{1,6}\s+baz1", - msg="dateformat - first annotation task 3") + self.assertRegex( + out, + "one\n.+\\d{1,6}\\s+\\d{1,6}\\s+foo1", + msg="dateformat - first annotation task 1", + ) + self.assertRegex( + out, + "foo1\n.+\\d{1,6}\\s+\\d{1,6}\\s+foo2", + msg="dateformat - second annotation task 1", + ) + self.assertRegex( + out, + "foo2\n.+\\d{1,6}\\s+\\d{1,6}\\s+foo3", + msg="dateformat - third annotation task 1", + ) + self.assertRegex( + out, + "two\n.+\\d{1,6}\\s+\\d{1,6}\\s+bar1", + msg="dateformat - first annotation task 2", + ) + self.assertRegex( + out, + "bar1\n.+\\d{1,6}\\s+\\d{1,6}\\s+bar2", + msg="dateformat - second annotation task 2", + ) + self.assertRegex( + out, + "three\n.+\\d{1,6}\\s+\\d{1,6}\\s+baz1", + msg="dateformat - first annotation task 3", + ) + class TestAnnotationPropagation(TestCase): def setUp(self): @@ -139,7 +176,7 @@ class TestAnnotationPropagation(TestCase): def test_annotate_recurring(self): """Test propagation of annotation to recurring siblings""" self.t("add foo due:eom recur:weekly") - self.t("list") # GC/handleRecurrence + self.t("list") # GC/handleRecurrence self.t("2 annotate bar", input="y\n") code, out, err = self.t("all rc.verbose:nothing") @@ -195,6 +232,7 @@ class TestBug495(TestCase): code, out, err = self.t("_get 1.annotations.1.description") self.assertEqual("This is -- a -- test\n", out) + class TestBug694(TestCase): def setUp(self): self.t = Task() @@ -212,6 +250,7 @@ class TestBug694(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/append.t b/test/append.test.py similarity index 94% rename from test/append.t rename to test/append.test.py index b2fa85f66..512c00355 100755 --- a/test/append.t +++ b/test/append.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -50,7 +50,7 @@ class TestAppend(TestCase): code, out, err = self.t("info 1") - expected = "Description\s+foo\sbar\n" + expected = "Description\\s+foo\\sbar\n" self.assertRegex(out, expected) def test_append_error_on_empty(self): @@ -82,14 +82,15 @@ class TestBug440(TestCase): code2, out2, err2 = self.t("2 ls") self.assertNotIn("Foo", out1) - self.assertRegex(out1, "\w+ Appendtext") + self.assertRegex(out1, r"\w+ Appendtext") self.assertNotIn("Foo", out2) - self.assertRegex(out2, "\w+ Appendtext") + self.assertRegex(out2, r"\w+ Appendtext") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/args.t b/test/args.test.py similarity index 84% rename from test/args.t rename to test/args.test.py index 875ae61dd..31281bb08 100755 --- a/test/args.t +++ b/test/args.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -45,27 +45,33 @@ class TestArgs(TestCase): self.t("add project:p pri:H +tag foo") code, out, err = self.t("_get 1.description") - self.assertIn("foo\n", out, msg='add project:p pri:H +tag foo') + self.assertIn("foo\n", out, msg="add project:p pri:H +tag foo") self.t("1 modify project:p pri:H +tag -- foo") code, out, err = self.t("_get 1.description") - self.assertIn("foo\n", out, msg='add project:p pri:H +tag -- foo') + self.assertIn("foo\n", out, msg="add project:p pri:H +tag -- foo") self.t("1 modify project:p pri:H -- +tag foo") code, out, err = self.t("_get 1.description") - self.assertIn("+tag foo\n", out, msg='add project:p pri:H -- +tag foo') + self.assertIn("+tag foo\n", out, msg="add project:p pri:H -- +tag foo") self.t("1 modify project:p -- pri:H +tag foo") code, out, err = self.t("_get 1.description") - self.assertIn("pri:H +tag foo\n", out, msg='add project:p -- pri:H +tag foo') + self.assertIn("pri:H +tag foo\n", out, msg="add project:p -- pri:H +tag foo") self.t("1 modify -- project:p pri:H +tag foo") code, out, err = self.t("_get 1.description") - self.assertIn("project:p pri:H +tag foo\n", out, msg='add -- project:p pri:H +tag foo') + self.assertIn( + "project:p pri:H +tag foo\n", out, msg="add -- project:p pri:H +tag foo" + ) self.t("1 modify -- project:p pri:H +tag foo --") code, out, err = self.t("_get 1.description") - self.assertIn("project:p pri:H +tag foo --\n", out, msg='add -- project:p pri:H +tag foo --') + self.assertIn( + "project:p pri:H +tag foo --\n", + out, + msg="add -- project:p pri:H +tag foo --", + ) class TestIDPosition(TestCase): @@ -95,6 +101,7 @@ class TestIDPosition(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/basetest/CMakeLists.txt b/test/basetest/CMakeLists.txt new file mode 100644 index 000000000..67daf1190 --- /dev/null +++ b/test/basetest/CMakeLists.txt @@ -0,0 +1,8 @@ +configure_file(__init__.py __init__.py COPYONLY) +configure_file(compat.py compat.py COPYONLY) +configure_file(exceptions.py exceptions.py COPYONLY) +configure_file(hooks.py hooks.py COPYONLY) +configure_file(meta.py meta.py COPYONLY) +configure_file(task.py task.py COPYONLY) +configure_file(testing.py testing.py COPYONLY) +configure_file(utils.py utils.py) diff --git a/test/basetest/__init__.py b/test/basetest/__init__.py index 08aa9ac92..4a97018a5 100644 --- a/test/basetest/__init__.py +++ b/test/basetest/__init__.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from .task import Task from .testing import TestCase diff --git a/test/basetest/compat.py b/test/basetest/compat.py index e60cb97a3..33696e8d0 100644 --- a/test/basetest/compat.py +++ b/test/basetest/compat.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - try: STRING_TYPE = basestring except NameError: diff --git a/test/basetest/exceptions.py b/test/basetest/exceptions.py index c960442bc..ccab35a2d 100644 --- a/test/basetest/exceptions.py +++ b/test/basetest/exceptions.py @@ -1,26 +1,29 @@ -# -*- coding: utf-8 -*- import signal -sig_names = dict((k, v) for v, k in reversed(sorted(signal.__dict__.items())) - if v.startswith('SIG') and not v.startswith('SIG_')) +sig_names = dict( + (k, v) + for v, k in reversed(sorted(signal.__dict__.items())) + if v.startswith("SIG") and not v.startswith("SIG_") +) class CommandError(Exception): def __init__(self, cmd, code, out, err=None, msg=None): - DEFAULT = ("Command '{{0}}' was {signal}'ed. " - "SIGABRT usually means task timed out.\n") + DEFAULT = ( + "Command '{{0}}' was {signal}'ed. " + "SIGABRT usually means task timed out.\n" + ) if msg is None: msg_suffix = "\n*** Start STDOUT ***\n{2}\n*** End STDOUT ***\n" if err is not None: - msg_suffix += ( - "\n*** Start STDERR ***\n{3}\n*** End STDERR ***\n" - ) + msg_suffix += "\n*** Start STDERR ***\n{3}\n*** End STDERR ***\n" if code < 0: self.msg = DEFAULT.format(signal=sig_names[abs(code)]) else: - self.msg = ("Command '{0}' finished with unexpected exit " - "code '{1}'.\n") + self.msg = ( + "Command '{0}' finished with unexpected exit " "code '{1}'.\n" + ) self.msg += msg_suffix else: @@ -44,12 +47,12 @@ class TimeoutWaitingFor(object): self.name = name def __repr__(self): - return "*** Timeout reached while waiting for {0} ***".format( - self.name) + return "*** Timeout reached while waiting for {0} ***".format(self.name) class StreamsAreMerged(object): def __repr__(self): return "*** Streams are merged, STDERR is not available ***" + # vim: ai sts=4 et sw=4 diff --git a/test/basetest/hooks.py b/test/basetest/hooks.py index e0f7ea7ad..9332dc7b0 100644 --- a/test/basetest/hooks.py +++ b/test/basetest/hooks.py @@ -1,11 +1,10 @@ -# -*- coding: utf-8 -*- - from __future__ import division import errno import os from sys import stderr import shutil import stat + try: import simplejson as json except ImportError: @@ -17,8 +16,8 @@ from .exceptions import HookError class InvalidJSON(object): - """Object representing the original unparsed JSON string and the JSON error - """ + """Object representing the original unparsed JSON string and the JSON error""" + def __init__(self, original, error): self.original = original self.error = error @@ -40,6 +39,7 @@ class Hooks(object): """Abstraction to help interact with hooks (add, remove) during tests and keep track of which are active. """ + def __init__(self, datadir): """Initialize hooks container which keeps track of active hooks and @@ -65,8 +65,7 @@ class Hooks(object): enabled = ", ".join(enabled) or None disabled = ", ".join(disabled) or None - return "".format(enabled, - disabled) + return "".format(enabled, disabled) def __getitem__(self, name): return self._hooks[name] @@ -136,8 +135,7 @@ class Hooks(object): hook._delete() def clear(self): - """Remove all existing hooks and empty the hook registry - """ + """Remove all existing hooks and empty the hook registry""" self._hooks = {} # Remove any existing hooks @@ -152,10 +150,11 @@ class Hooks(object): class Hook(object): - """Represents a hook script and provides methods to enable/disable hooks - """ - def __init__(self, hookname, hookdir, content=None, default=False, - default_hookpath=None): + """Represents a hook script and provides methods to enable/disable hooks""" + + def __init__( + self, hookname, hookdir, content=None, default=False, default_hookpath=None + ): """Initialize and create the hook This class supports creating hooks in two ways: @@ -183,24 +182,25 @@ class Hook(object): self._check_hook_not_exists(self.hookfile) if not default and content is None: - raise HookError("Cannot create hookfile {0} without content. " - "If using a builtin hook pass default=True" - .format(self.hookname)) + raise HookError( + "Cannot create hookfile {0} without content. " + "If using a builtin hook pass default=True".format(self.hookname) + ) if os.path.isfile(self.hookfile): - raise HookError("Hook with name {0} already exists. " - "Did you forget to remove() it before recreating?" - .format(self.hookname)) + raise HookError( + "Hook with name {0} already exists. " + "Did you forget to remove() it before recreating?".format(self.hookname) + ) if default: - self.default_hookfile = os.path.join(self.default_hookpath, - self.hookname) + self.default_hookfile = os.path.join(self.default_hookpath, self.hookname) self._check_hook_exists(self.default_hookfile) # Symlinks change permission of source file, cannot use one here shutil.copy(self.default_hookfile, self.hookfile) else: self.default_hookfile = None - with open(self.hookfile, 'w') as fh: + with open(self.hookfile, "w") as fh: fh.write(content) def __eq__(self, other): @@ -252,16 +252,19 @@ class Hook(object): if self.hookname.startswith(hooktype): break else: - stderr.write("WARNING: {0} is not a valid hook type. " - "It will not be triggered\n".format(self.hookname)) + stderr.write( + "WARNING: {0} is not a valid hook type. " + "It will not be triggered\n".format(self.hookname) + ) def _remove_file(self, file): try: os.remove(file) except OSError as e: if e.errno == errno.ENOENT: - raise HookError("Hook with name {0} was not found on " - "hooks/ folder".format(file)) + raise HookError( + "Hook with name {0} was not found on " "hooks/ folder".format(file) + ) else: raise @@ -273,18 +276,15 @@ class Hook(object): self._remove_hookfile(self.hookfile) def enable(self): - """Make hookfile executable to allow triggering - """ + """Make hookfile executable to allow triggering""" os.chmod(self.hookfile, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) def disable(self): - """Remove hookfile executable bit to deny triggering - """ + """Remove hookfile executable bit to deny triggering""" os.chmod(self.hookfile, stat.S_IREAD | stat.S_IWRITE) def is_active(self): - """Check if hook is active by verifying the execute bit - """ + """Check if hook is active by verifying the execute bit""" return os.access(self.hookfile, os.X_OK) @@ -292,6 +292,7 @@ class LoggedHook(Hook): """A variant of a Hook that allows checking that the hook was called, what was received via STDIN and what was answered to STDOUT """ + def __init__(self, *args, **kwargs): super(LoggedHook, self).__init__(*args, **kwargs) @@ -302,8 +303,7 @@ class LoggedHook(Hook): self.wrappedname = "original_" + self.hookname self.wrappedfile = os.path.join(self.hookdir, self.wrappedname) - self.original_wrapper = os.path.join(self.default_hookpath, - "wrapper.sh") + self.original_wrapper = os.path.join(self.default_hookpath, "wrapper.sh") self.hooklog_in = self.wrappedfile + ".log.in" self.hooklog_out = self.wrappedfile + ".log.out" @@ -328,11 +328,10 @@ class LoggedHook(Hook): self._remove_file(self.hooklog_out) def _setup_wrapper(self): - """Setup wrapper shell script to allow capturing input/output of hook - """ + """Setup wrapper shell script to allow capturing input/output of hook""" # Create empty hooklog to allow checking that hook executed - open(self.hooklog_in, 'w').close() - open(self.hooklog_out, 'w').close() + open(self.hooklog_in, "w").close() + open(self.hooklog_out, "w").close() # Rename the original hook to the name that will be used by wrapper self._check_hook_not_exists(self.wrappedfile) @@ -342,8 +341,7 @@ class LoggedHook(Hook): shutil.copy(self.original_wrapper, self.hookfile) def _get_log_stat(self): - """Return the most recent change timestamp and size of both logfiles - """ + """Return the most recent change timestamp and size of both logfiles""" stdin = os.stat(self.hooklog_in) stdout = os.stat(self.hooklog_out) @@ -351,8 +349,7 @@ class LoggedHook(Hook): return last_change, stdin.st_size, stdout.st_size def _use_cache(self): - """Check if log files were changed since last check - """ + """Check if log files were changed since last check""" try: last_change = self._cache["last_change"] except KeyError: @@ -369,20 +366,17 @@ class LoggedHook(Hook): return True def enable(self): - """Make hookfile executable to allow triggering - """ + """Make hookfile executable to allow triggering""" super(LoggedHook, self).enable() os.chmod(self.wrappedfile, stat.S_IREAD | stat.S_IWRITE | stat.S_IEXEC) def disable(self): - """Remove hookfile executable bit to deny triggering - """ + """Remove hookfile executable bit to deny triggering""" super(LoggedHook, self).disable() os.chmod(self.wrappedfile, stat.S_IREAD | stat.S_IWRITE) def is_active(self): - """Check if hook is active by verifying the execute bit - """ + """Check if hook is active by verifying the execute bit""" parent_is_active = super(LoggedHook, self).disable() return parent_is_active and os.access(self.wrappedfile, os.X_OK) @@ -409,16 +403,17 @@ class LoggedHook(Hook): if self._use_cache(): return self._cache["log"] - log = {"calls": [], - "input": { - "json": [], - }, - "output": { - "json": [], - "msgs": [], - }, - "exitcode": None, - } + log = { + "calls": [], + "input": { + "json": [], + }, + "output": { + "json": [], + "msgs": [], + }, + "exitcode": None, + } with open(self.hooklog_in) as fh: for i, line in enumerate(fh): @@ -428,16 +423,19 @@ class LoggedHook(Hook): # Timestamp includes nanosecond resolution timestamp = tstamp.split(" ")[-1] # convert timestamp to python datetime object - log["calls"].append({ - "timestamp": datetime.fromtimestamp(float(timestamp)), - "args": args, - }) + log["calls"].append( + { + "timestamp": datetime.fromtimestamp(float(timestamp)), + "args": args, + } + ) elif line.startswith("{"): # Decode json input (to hook) log["input"]["json"].append(json_decoder(line)) else: - raise IOError("Unexpected content on STDIN line {0}: {1}" - .format(i, line)) + raise IOError( + "Unexpected content on STDIN line {0}: {1}".format(i, line) + ) with open(self.hooklog_out) as fh: for line in fh: @@ -466,49 +464,43 @@ class LoggedHook(Hook): """ log = self.get_logs() - assert len(log["calls"]) == count, ("{0} calls expected for {1} but " - "found {2}".format( - count, - self.hookname, - log["calls"] - )) + assert ( + len(log["calls"]) == count + ), "{0} calls expected for {1} but " "found {2}".format( + count, self.hookname, log["calls"] + ) def assertExitcode(self, exitcode): - """Check if current hook finished with the expected exit code - """ + """Check if current hook finished with the expected exit code""" log = self.get_logs() - assert log["exitcode"] == exitcode, ("Expected exit code {0} for {1} " - "but found {2}".format( - exitcode, - self.hookname, - log["exitcode"] - )) + assert ( + log["exitcode"] == exitcode + ), "Expected exit code {0} for {1} " "but found {2}".format( + exitcode, self.hookname, log["exitcode"] + ) def assertValidJSONOutput(self): - """Check if current hook output is valid JSON in all expected replies - """ + """Check if current hook output is valid JSON in all expected replies""" log = self.get_logs() for i, out in enumerate(log["output"]["json"]): - assert not isinstance(out, InvalidJSON), ("Invalid JSON found at " - "reply number {0} with " - "content {1}".format( - i + 1, - out.original - )) + assert not isinstance(out, InvalidJSON), ( + "Invalid JSON found at " + "reply number {0} with " + "content {1}".format(i + 1, out.original) + ) def assertInvalidJSONOutput(self): - """Check if current hook output is invalid JSON in any expected reply - """ + """Check if current hook output is invalid JSON in any expected reply""" log = self.get_logs() for i, out in enumerate(log["output"]["json"]): - assert isinstance(out, InvalidJSON), ("Valid JSON found at reply " - "number {0} with content " - "{1}".format( - i + 1, - out.original - )) + assert isinstance(out, InvalidJSON), ( + "Valid JSON found at reply " + "number {0} with content " + "{1}".format(i + 1, out.original) + ) + # vim: ai sts=4 et sw=4 diff --git a/test/basetest/meta.py b/test/basetest/meta.py index 2be78665e..f5b30a5ad 100644 --- a/test/basetest/meta.py +++ b/test/basetest/meta.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - from __future__ import print_function, division @@ -9,6 +7,7 @@ class MetaTest(type): Creates test_methods in the TestCase class dynamically named after the arguments used. """ + @staticmethod def make_function(classname, *args, **kwargs): def test(self): @@ -37,4 +36,5 @@ class MetaTest(type): return super(MetaTest, meta).__new__(meta, classname, bases, dct) + # vim: ai sts=4 et sw=4 diff --git a/test/basetest/task.py b/test/basetest/task.py index f6afc9fea..be30441b7 100644 --- a/test/basetest/task.py +++ b/test/basetest/task.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import atexit import errno import json @@ -23,6 +21,7 @@ class Task(object): A taskw client should not be used after being destroyed. """ + DEFAULT_TASK = task_binary_location() def __init__(self, taskw=DEFAULT_TASK): @@ -45,11 +44,13 @@ class Task(object): self.reset_env() - with open(self.taskrc, 'w') as rc: - rc.write("data.location={0}\n" - "hooks=off\n" - "news.version=2.6.0\n" - "".format(self.datadir)) + with open(self.taskrc, "w") as rc: + rc.write( + "data.location={0}\n" + "hooks=off\n" + "news.version=2.6.0\n" + "".format(self.datadir) + ) # Hooks disabled until requested self.hooks = None @@ -63,14 +64,12 @@ class Task(object): return self.runSuccess(*args, **kwargs) def activate_hooks(self): - """Enable self.hooks functionality and activate hooks on config - """ + """Enable self.hooks functionality and activate hooks on config""" self.config("hooks", "1") self.hooks = Hooks(self.datadir) def reset_env(self): - """Set a new environment derived from the one used to launch the test - """ + """Set a new environment derived from the one used to launch the test""" # Copy all env variables to avoid clashing subprocess environments self.env = os.environ.copy() @@ -80,15 +79,13 @@ class Task(object): self.env["TASKRC"] = self.taskrc def config(self, var, value): - """Run setup `var` as `value` in taskd config - """ + """Run setup `var` as `value` in taskd config""" # Add -- to avoid misinterpretation of - in things like UUIDs cmd = (self.taskw, "config", "--", var, value) return run_cmd_wait(cmd, env=self.env, input="y\n") def del_config(self, var): - """Remove `var` from taskd config - """ + """Remove `var` from taskd config""" cmd = (self.taskw, "config", var) return run_cmd_wait(cmd, env=self.env, input="y\n") @@ -106,8 +103,9 @@ class Task(object): if export_filter is None: export_filter = "" - code, out, err = self.runSuccess("rc.json.array=1 {0} export" - "".format(export_filter)) + code, out, err = self.runSuccess( + "rc.json.array=1 {0} export" "".format(export_filter) + ) return json.loads(out) @@ -120,16 +118,16 @@ class Task(object): result = self.export(export_filter=export_filter) if len(result) != 1: - descriptions = [task.get('description') or '[description-missing]' - for task in result] + descriptions = [ + task.get("description") or "[description-missing]" for task in result + ] raise ValueError( "One task should match the '{0}' filter, '{1}' " "matches:\n {2}".format( - export_filter or '', - len(result), - '\n '.join(descriptions) - )) + export_filter or "", len(result), "\n ".join(descriptions) + ) + ) return result[0] @@ -148,8 +146,7 @@ class Task(object): return args - def runSuccess(self, args="", input=None, merge_streams=False, - timeout=5): + def runSuccess(self, args="", input=None, merge_streams=False, timeout=5): """Invoke task with given arguments and fail if exit code != 0 Use runError if you want exit_code to be tested automatically and @@ -173,10 +170,9 @@ class Task(object): args = self._split_string_args_if_string(args) command.extend(args) - output = run_cmd_wait_nofail(command, input, - merge_streams=merge_streams, - env=self.env, - timeout=timeout) + output = run_cmd_wait_nofail( + command, input, merge_streams=merge_streams, env=self.env, timeout=timeout + ) if output[0] != 0: raise CommandError(command, *output) @@ -207,10 +203,9 @@ class Task(object): args = self._split_string_args_if_string(args) command.extend(args) - output = run_cmd_wait_nofail(command, input, - merge_streams=merge_streams, - env=self.env, - timeout=timeout) + output = run_cmd_wait_nofail( + command, input, merge_streams=merge_streams, env=self.env, timeout=timeout + ) # output[0] is the exit code if output[0] == 0 or output[0] is None: @@ -219,8 +214,7 @@ class Task(object): return output def destroy(self): - """Cleanup the data folder and release server port for other instances - """ + """Cleanup the data folder and release server port for other instances""" try: shutil.rmtree(self.datadir) except OSError as e: @@ -239,8 +233,10 @@ class Task(object): self.destroy = lambda: None def __destroyed(self, *args, **kwargs): - raise AttributeError("Task instance has been destroyed. " - "Create a new instance if you need a new client.") + raise AttributeError( + "Task instance has been destroyed. " + "Create a new instance if you need a new client." + ) def diag(self, merge_streams_with=None): """Run task diagnostics. @@ -304,4 +300,5 @@ class Task(object): # Use advanced time format self._command = [cmd, "-f", faketime] + self._command + # vim: ai sts=4 et sw=4 diff --git a/test/basetest/testing.py b/test/basetest/testing.py index 5d557df8a..56154f925 100644 --- a/test/basetest/testing.py +++ b/test/basetest/testing.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import unittest import sys from .utils import TASKW_SKIP @@ -9,14 +7,14 @@ class BaseTestCase(unittest.TestCase): def tap(self, out): sys.stderr.write("--- tap output start ---\n") for line in out.splitlines(): - sys.stderr.write(line + '\n') + sys.stderr.write(line + "\n") sys.stderr.write("--- tap output end ---\n") @unittest.skipIf(TASKW_SKIP, "TASKW_SKIP set, skipping task tests.") class TestCase(BaseTestCase): - """Automatically skips tests if TASKW_SKIP is present in the environment - """ + """Automatically skips tests if TASKW_SKIP is present in the environment""" + pass diff --git a/test/basetest/utils.py b/test/basetest/utils.py index 0c8a941d0..d2ba9aae0 100644 --- a/test/basetest/utils.py +++ b/test/basetest/utils.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from __future__ import division import errno import os @@ -10,11 +9,13 @@ import atexit import tempfile from subprocess import Popen, PIPE, STDOUT from threading import Thread + try: from Queue import Queue, Empty except ImportError: from queue import Queue, Empty from time import sleep + try: import simplejson as json except ImportError: @@ -22,33 +23,32 @@ except ImportError: from .exceptions import CommandError, TimeoutWaitingFor USED_PORTS = set() -ON_POSIX = 'posix' in sys.builtin_module_names +ON_POSIX = "posix" in sys.builtin_module_names # Directory relative to basetest module location CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) -# Location of binary files (usually the src/ folder) -BIN_PREFIX = os.path.abspath( - os.path.join(CURRENT_DIR, "..", "..", "src") -) +# From the CMAKE value of the same name. This is substituted at configure. +CMAKE_BINARY_DIR = os.path.abspath("${CMAKE_BINARY_DIR}") -# Default location of test certificates -DEFAULT_CERT_PATH = os.path.abspath( - os.path.join(CURRENT_DIR, "..", "test_certs") -) +# Location of binary files (usually the src/ folder) +BIN_PREFIX = os.path.abspath(os.path.join(CMAKE_BINARY_DIR, "src")) # Default location of test hooks DEFAULT_HOOK_PATH = os.path.abspath( - os.path.join(CURRENT_DIR, "..", "test_hooks") + os.path.join("${CMAKE_SOURCE_DIR}", "test", "test_hooks") ) +# Source directory +SOURCE_DIR = os.path.abspath("${CMAKE_SOURCE_DIR}") + # Environment flags to control skipping of task tests TASKW_SKIP = os.environ.get("TASKW_SKIP", False) # Environment flags to control use of PATH or in-tree binaries TASK_USE_PATH = os.environ.get("TASK_USE_PATH", False) -UUID_REGEXP = ("[0-9A-Fa-f]{8}-" + ("[0-9A-Fa-f]{4}-" * 3) + "[0-9A-Fa-f]{12}") +UUID_REGEXP = "[0-9A-Fa-f]{8}-" + ("[0-9A-Fa-f]{4}-" * 3) + "[0-9A-Fa-f]{12}" def task_binary_location(cmd="task"): @@ -68,13 +68,12 @@ def binary_location(cmd, USE_PATH=False): return os.path.join(BIN_PREFIX, cmd) -def wait_condition(cond, timeout=1, sleeptime=.01): - """Wait for condition to return anything other than None - """ +def wait_condition(cond, timeout=10, sleeptime=0.01): + """Wait for condition to return anything other than None""" # NOTE Increasing sleeptime can dramatically increase testsuite runtime # It also reduces CPU load significantly if timeout is None: - timeout = 1 + timeout = 10 if timeout < sleeptime: print("Warning, timeout cannot be smaller than", sleeptime) @@ -95,8 +94,8 @@ def wait_condition(cond, timeout=1, sleeptime=.01): def wait_process(pid, timeout=None): - """Wait for process to finish - """ + """Wait for process to finish""" + def process(): try: os.kill(pid, 0) @@ -123,13 +122,18 @@ def _queue_output(arguments, pidq, outputq): # pid None is read by the main thread as a crash of the process pidq.put(None) - outputq.put(( - "", - ("Unexpected exception caught during execution of taskw: '{0}' . " - "If you are running out-of-tree tests set TASK_USE_PATH=1 " - "in shell env before execution and add the " - "location of the task(d) binary to the PATH".format(e)), - 255)) # false exitcode + outputq.put( + ( + "", + ( + "Unexpected exception caught during execution of taskw: '{0}' . " + "If you are running out-of-tree tests set TASK_USE_PATH=1 " + "in shell env before execution and add the " + "location of the task(d) binary to the PATH".format(e) + ), + 255, + ) + ) # false exitcode return @@ -140,15 +144,14 @@ def _queue_output(arguments, pidq, outputq): out, err = proc.communicate(input_data) if sys.version_info > (3,): - out, err = out.decode('utf-8'), err.decode('utf-8') + out, err = out.decode("utf-8"), err.decode("utf-8") # Give the output back to the caller outputq.put((out, err, proc.returncode)) def _retrieve_output(thread, timeout, queue, thread_error): - """Fetch output from taskw subprocess queues - """ + """Fetch output from taskw subprocess queues""" # Try to join the thread on failure abort thread.join(timeout) if thread.is_alive(): @@ -187,16 +190,16 @@ def _get_output(arguments, timeout=None): # Process crashed or timed out for some reason if pid is None: - return _retrieve_output(t, output_timeout, outputq, - "TaskWarrior to start") + return _retrieve_output(t, output_timeout, outputq, "TaskWarrior to start") # Wait for process to finish (normal execution) state = wait_process(pid, timeout) if state: # Process finished - return _retrieve_output(t, output_timeout, outputq, - "TaskWarrior thread to join") + return _retrieve_output( + t, output_timeout, outputq, "TaskWarrior thread to join" + ) # If we reach this point we assume the process got stuck or timed out for sig in (signal.SIGABRT, signal.SIGTERM, signal.SIGKILL): @@ -213,15 +216,21 @@ def _get_output(arguments, timeout=None): if state: # Process finished - return _retrieve_output(t, output_timeout, outputq, - "TaskWarrior to die") + return _retrieve_output(t, output_timeout, outputq, "TaskWarrior to die") # This should never happen but in case something goes really bad raise OSError("TaskWarrior stopped responding and couldn't be killed") -def run_cmd_wait(cmd, input=None, stdout=PIPE, stderr=PIPE, - merge_streams=False, env=os.environ, timeout=None): +def run_cmd_wait( + cmd, + input=None, + stdout=PIPE, + stderr=PIPE, + merge_streams=False, + env=os.environ, + timeout=None, +): "Run a subprocess and wait for it to finish" if input is None: @@ -268,8 +277,7 @@ def run_cmd_wait_nofail(*args, **kwargs): def memoize(obj): - """Keep an in-memory cache of function results given its inputs - """ + """Keep an in-memory cache of function results given its inputs""" cache = obj.cache = {} @functools.wraps(obj) @@ -278,11 +286,13 @@ def memoize(obj): if key not in cache: cache[key] = obj(*args, **kwargs) return cache[key] + return memoizer try: from shutil import which + which = memoize(which) except ImportError: # NOTE: This is shutil.which backported from python-3.3.3 @@ -297,12 +307,12 @@ except ImportError: path. """ + # Check that a given file can be accessed with the correct mode. # Additionally check that `file` is not a directory, as on Windows # directories pass the os.access check. def _access_check(fn, mode): - return (os.path.exists(fn) and os.access(fn, mode) and - not os.path.isdir(fn)) + return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) # If we're given a path with a directory part, look it up directly # rather than referring to PATH directories. This includes checking @@ -351,16 +361,15 @@ except ImportError: def parse_datafile(file): - """Parse .data files on the client and server treating files as JSON - """ + """Parse .data files on the client and server treating files as JSON""" data = [] with open(file) as fh: for line in fh: line = line.rstrip("\n") # Turn [] strings into {} to be treated properly as JSON hashes - if line.startswith('[') and line.endswith(']'): - line = '{' + line[1:-1] + '}' + if line.startswith("[") and line.endswith("]"): + line = "{" + line[1:-1] + "}" if line.startswith("{"): data.append(json.loads(line)) @@ -373,6 +382,7 @@ def mkstemp(data): """ Create a temporary file that is removed at process exit """ + def rmtemp(name): try: os.remove(name) @@ -380,7 +390,7 @@ def mkstemp(data): pass f = tempfile.NamedTemporaryFile(delete=False) - f.write(data.encode('utf-8') if not isinstance(data, bytes) else data) + f.write(data.encode("utf-8") if not isinstance(data, bytes) else data) f.close() # Ensure removal at end of python session @@ -390,11 +400,11 @@ def mkstemp(data): def mkstemp_exec(data): - """Create a temporary executable file that is removed at process exit - """ + """Create a temporary executable file that is removed at process exit""" name = mkstemp(data) os.chmod(name, 0o755) return name + # vim: ai sts=4 et sw=4 diff --git a/test/bash_completion.t b/test/bash_completion.test.py similarity index 92% rename from test/bash_completion.t rename to test/bash_completion.test.py index c2e479bf2..360fe7d8d 100755 --- a/test/bash_completion.t +++ b/test/bash_completion.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,13 +29,14 @@ import sys import os import unittest from contextlib import contextmanager + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase from basetest.utils import BIN_PREFIX -TASKSH = os.path.abspath(os.path.join(BIN_PREFIX, "..", "scripts/bash/task.sh")) +TASKSH = os.path.abspath(os.path.join(BIN_PREFIX, "..", "..", "scripts/bash/task.sh")) @contextmanager @@ -67,16 +67,18 @@ def prepare_tasksh(t): tasksh.append(line) - tasksh.extend([ - 'COMP_WORDS=("$@")', - 'COMP_CWORD=$(($#-1))', - '_task', - 'for reply_iter in "${COMPREPLY[@]}"; do', - ' echo $reply_iter', - 'done', - ]) + tasksh.extend( + [ + 'COMP_WORDS=("$@")', + "COMP_CWORD=$(($#-1))", + "_task", + 'for reply_iter in "${COMPREPLY[@]}"; do', + " echo $reply_iter", + "done", + ] + ) - return '\n'.join(tasksh) + return "\n".join(tasksh) class TestBashCompletionBase(TestCase): @@ -89,7 +91,7 @@ class TestBashCompletionBase(TestCase): self.t.tasksh_script = os.path.join(self.t.datadir, "task.sh") - with open(self.t.tasksh_script, 'w') as tasksh: + with open(self.t.tasksh_script, "w") as tasksh: tasksh.write(prepare_tasksh(self.t)) @@ -171,6 +173,7 @@ class TestProject(TestBashCompletionBase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/bash_tap.sh b/test/bash_tap.sh index ebf51684a..c60343ca1 100644 --- a/test/bash_tap.sh +++ b/test/bash_tap.sh @@ -8,13 +8,7 @@ function bashtap_on_error { # $bashtap_line contains the last executed line, or an error. echo -n "$bashtap_output" - # Determine if this failure was expected - if [[ ! -z "$EXPFAIL" ]] - then - todo_suffix=" # TODO" - fi - - echo "not ok 1 - ${bashtap_line}${todo_suffix}" + echo "not ok 1 - ${bashtap_line}" bashtap_clean_tmpdir } @@ -81,41 +75,35 @@ function bashtap_get_absolute_path { bashtap_org_pwd=$(pwd) bashtap_org_script=$(bashtap_get_absolute_path "$0") -if [ "${0:(-2)}" == ".t" ] || [ "$1" == "-t" ]; then - # Make sure any failing commands are caught. - set -e - set -o pipefail +# Make sure any failing commands are caught. +set -e +set -o pipefail - # TAP header. Hardcoded number of tests, 1. - echo "1..1" +# TAP header. Hardcoded number of tests, 1. +echo "1..1" - # Output TAP failure on early exit. - trap bashtap_on_error EXIT +# Output TAP failure on early exit. +trap bashtap_on_error EXIT - # The different calls to mktemp are necessary for OSX compatibility. - bashtap_tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'bash_tap') - if [ ! -z "$bashtap_tmpdir" ]; then - cd "$bashtap_tmpdir" - else - bashtap_line="Unable to create temporary directory." - exit 1 - fi - - # Scripts sourced before bash_tap.sh may declare this function. - if declare -f bashtap_setup >/dev/null; then - bashtap_setup - fi - - # Run test file interpreting failing commands as a test failure. - bashtap_run_testcase && echo "ok 1" - - # Since we're in a sourced file and just ran the parent script, - # exit without running it a second time. - trap - EXIT - bashtap_clean_tmpdir - exit +# The different calls to mktemp are necessary for OSX compatibility. +bashtap_tmpdir=$(mktemp -d 2>/dev/null || mktemp -d -t 'bash_tap') +if [ ! -z "$bashtap_tmpdir" ]; then + cd "$bashtap_tmpdir" else - if declare -f bashtap_setup >/dev/null; then - bashtap_setup - fi + bashtap_line="Unable to create temporary directory." + exit 1 fi + +# Scripts sourced before bash_tap.sh may declare this function. +if declare -f bashtap_setup >/dev/null; then + bashtap_setup +fi + +# Run test file interpreting failing commands as a test failure. +bashtap_run_testcase && echo "ok 1" + +# Since we're in a sourced file and just ran the parent script, +# exit without running it a second time. +trap - EXIT +bashtap_clean_tmpdir +exit diff --git a/test/bash_tap_tw.sh b/test/bash_tap_tw.sh index 4cdf6033e..98da797c9 100644 --- a/test/bash_tap_tw.sh +++ b/test/bash_tap_tw.sh @@ -13,7 +13,7 @@ function setup_taskrc { # Configuration - for i in pending.data completed.data undo.data backlog.data taskrc; do + for i in taskchampion.sqlite3 taskrc; do if [ -f "$i" ]; then rm "$i" 2>&1 >/dev/null fi @@ -26,6 +26,7 @@ function setup_taskrc { echo 'color.header=rgb025' >> taskrc echo 'color.footer=rgb025' >> taskrc echo 'color.error=bold white on red' >> taskrc + echo 'news.version=99.0.0' >> taskrc } function find_task_binary { diff --git a/test/blocked.t b/test/blocked.test.py similarity index 98% rename from test/blocked.t rename to test/blocked.test.py index 6e63623af..0eae3d2c4 100755 --- a/test/blocked.t +++ b/test/blocked.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -50,6 +50,7 @@ class TestBug1381(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/bulk.t b/test/bulk.test.py similarity index 98% rename from test/bulk.t rename to test/bulk.test.py index a66620169..e66c13aad 100755 --- a/test/bulk.t +++ b/test/bulk.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import signal import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -139,7 +139,9 @@ class TestBulk(TestCase): self.assertNotIn("Deleting task", out) # Test with 3 tasks, denying delete. - code, out, err = self.t.runError("1-3 delete rc.confirmation:1", input="n\nn\nn\n") + code, out, err = self.t.runError( + "1-3 delete rc.confirmation:1", input="n\nn\nn\n" + ) self.assertNotIn("(yes/no)", out) self.assertIn("(yes/no/all/quit)", out) self.assertNotIn("Deleted task 1", out) @@ -209,6 +211,7 @@ class TestBugBulk(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/burndown.t b/test/burndown.test.py similarity index 99% rename from test/burndown.t rename to test/burndown.test.py index 2ea44011c..9c31cd7a3 100755 --- a/test/burndown.t +++ b/test/burndown.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -98,6 +98,7 @@ class TestBurndownCommand(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/calc.t b/test/calc.test.py similarity index 92% rename from test/calc.t rename to test/calc.test.py index a31746d3b..1eb10c72a 100755 --- a/test/calc.t +++ b/test/calc.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -31,6 +30,7 @@ import os import re import signal import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,8 +40,9 @@ from basetest.utils import BIN_PREFIX, run_cmd_wait, run_cmd_wait_nofail CALC = os.path.join(BIN_PREFIX, "calc") -@unittest.skipIf(not os.path.isfile(CALC), - "calc binary not available in {0}".format(CALC)) +@unittest.skipIf( + not os.path.isfile(CALC), "calc binary not available in {0}".format(CALC) +) class TestCalc(TestCase): def test_regular_math(self): """regular math""" @@ -57,7 +58,9 @@ class TestCalc(TestCase): def test_postfix_math(self): """postfix math""" - code, out, err = run_cmd_wait((CALC, "--debug", "--postfix", "12 3600 * 34 60 * 56 + +")) + code, out, err = run_cmd_wait( + (CALC, "--debug", "--postfix", "12 3600 * 34 60 * 56 + +") + ) self.assertIn("Eval literal number ↑'12'", out) self.assertIn("Eval literal number ↑'3600'", out) @@ -90,7 +93,7 @@ class TestCalc(TestCase): """version""" code, out, err = run_cmd_wait_nofail((CALC, "--version")) - self.assertRegex(out, "calc \d\.\d+\.\d+") + self.assertRegex(out, r"calc \d\.\d+\.\d+") self.assertIn("Copyright", out) self.assertGreaterEqual(code, 1) @@ -123,18 +126,17 @@ class TestBug1254(TestCase): self.assertEqual(expected, code, "Exit code was non-zero ({0})".format(code)) def test_no_segmentation_fault_calc_negative_multiplication(self): - """1254: calc can multiply zero and negative numbers - """ + """1254: calc can multiply zero and negative numbers""" self.run_command("calc 0*-1") def test_calc_positive_multiplication(self): - """1254: calc can multiply negative zero and positive - """ + """1254: calc can multiply negative zero and positive""" self.run_command("calc 0*1") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/calendar.t b/test/calendar.test.py similarity index 89% rename from test/calendar.t rename to test/calendar.test.py index ddf30b1a6..40089df81 100755 --- a/test/calendar.t +++ b/test/calendar.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest from datetime import datetime, timedelta + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,6 +39,7 @@ from basetest import Task, TestCase def timestamp_in_holiday_format(time): return time.strftime("%Y%m%d") + class TestCalendarCommandLine(TestCase): def setUp(self): """Executed before each test in the class""" @@ -57,7 +58,9 @@ class TestCalendarCommandLine(TestCase): def test_basic_command_offset(self): """Verify 'calendar rc.calendar.offset:on rc.calendar.offset.value:1' does not fail""" - code, out, err = self.t("calendar rc.calendar.offset:on rc.calendar.offset.value:1") + code, out, err = self.t( + "calendar rc.calendar.offset:on rc.calendar.offset.value:1" + ) self.assertIn("Su Mo Tu We Th Fr Sa", out) def test_basic_command(self): @@ -73,13 +76,17 @@ class TestCalendarCommandLine(TestCase): def test_basic_command_details(self): """Verify 'calendar rc.calendar.details:full rc.calendar.details.report:list' does not fail""" self.t("add task_with_due_date due:tomorrow") - code, out, err = self.t("calendar rc.calendar.details:full rc.calendar.details.report:list") + code, out, err = self.t( + "calendar rc.calendar.details:full rc.calendar.details.report:list" + ) self.assertIn("task_with_due_date", out) def test_basic_command_details_color(self): """Verify 'calendar rc.calendar.details:full rc.calendar.details.report:list rc._forcecolor:on' does not fail""" self.t("add task_with_due_date due:tomorrow") - code, out, err = self.t("calendar rc.calendar.details:full rc.calendar.details.report:list rc._forcecolor:on") + code, out, err = self.t( + "calendar rc.calendar.details:full rc.calendar.details.report:list rc._forcecolor:on" + ) self.assertIn("task_with_due_date", out) def test_basic_command_holidays(self): @@ -89,29 +96,37 @@ class TestCalendarCommandLine(TestCase): def test_basic_command_single_holiday(self): """Verify 'calendar rc.holiday.test.name:donkeyday rc.holiday.test.date:[tomorrws date] rc.calendar.holidays:full' does not fail""" - code, out, err = self.t("calendar rc.holiday.test.name:donkeyday rc.holliday.test.date:{0} rc.calendar.holidays:full".format(self.tomorrow)) + code, out, err = self.t( + "calendar rc.holiday.test.name:donkeyday rc.holliday.test.date:{0} rc.calendar.holidays:full".format( + self.tomorrow + ) + ) self.assertRegex(out, "Date +Holiday") def test_basic_command_multiday_holiday(self): """Verify 'calendar rc.holiday.test.name:donkeyday rc.holiday.test.start:[tomorrws date] rc.holiday.test.end:[date a month later] rc.calendar.holidays:full' does not fail""" - code, out, err = self.t("calendar rc.holiday.test.name:donkeyday rc.holiday.test.start:{0} rc.holiday.test.end:{1} rc.calendar.holidays:full".format(self.tomorrow, self.next_month)) + code, out, err = self.t( + "calendar rc.holiday.test.name:donkeyday rc.holiday.test.start:{0} rc.holiday.test.end:{1} rc.calendar.holidays:full".format( + self.tomorrow, self.next_month + ) + ) self.assertRegex(out, "Date +Holiday") def test_y_argument(self): """Verify 'calendar y' does not fail""" code, out, err = self.t("calendar y") - self.assertIn("January", out) - self.assertIn("February", out) - self.assertIn("March", out) - self.assertIn("April", out) - self.assertIn("May", out) - self.assertIn("June", out) - self.assertIn("July", out) - self.assertIn("August", out) + self.assertIn("January", out) + self.assertIn("February", out) + self.assertIn("March", out) + self.assertIn("April", out) + self.assertIn("May", out) + self.assertIn("June", out) + self.assertIn("July", out) + self.assertIn("August", out) self.assertIn("September", out) - self.assertIn("October", out) - self.assertIn("November", out) - self.assertIn("December", out) + self.assertIn("October", out) + self.assertIn("November", out) + self.assertIn("December", out) self.assertNotIn("Could not recognize argument", err) def test_due_argument(self): @@ -250,6 +265,7 @@ class TestCalendarCommandLine(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/caseless.t b/test/caseless.test.py similarity index 87% rename from test/caseless.t rename to test/caseless.test.py index c2e800452..9e3ade09a 100755 --- a/test/caseless.t +++ b/test/caseless.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,11 +39,11 @@ class TestCaseless(TestCase): @classmethod def setUpClass(cls): """Executed once before any test in the class""" - cls.t = Task () + cls.t = Task() cls.t.config("report.ls.columns", "id,project,priority,description") - cls.t.config("report.ls.labels", "ID,Proj,Pri,Description") - cls.t.config("report.ls.sort", "priority-,project+") - cls.t.config("report.ls.filter", "status:pending") + cls.t.config("report.ls.labels", "ID,Proj,Pri,Description") + cls.t.config("report.ls.sort", "priority-,project+") + cls.t.config("report.ls.filter", "status:pending") def setUp(self): """Executed before each test in the class""" @@ -103,21 +103,30 @@ class TestCaseless(TestCase): def test_annotation_filter(self): """Verify annotation filter with and without case sensitivity""" - code, out, err = self.t.runError("rc.search.case.sensitive:yes ls description.contains:Three") + code, out, err = self.t.runError( + "rc.search.case.sensitive:yes ls description.contains:Three" + ) self.assertNotIn("one two three", out) - code, out, err = self.t("rc.search.case.sensitive:no ls description.contains:Three") + code, out, err = self.t( + "rc.search.case.sensitive:no ls description.contains:Three" + ) self.assertIn("one two three", out) - code, out, err = self.t.runError("rc.search.case.sensitive:yes ls description.contains:Six") + code, out, err = self.t.runError( + "rc.search.case.sensitive:yes ls description.contains:Six" + ) self.assertNotIn("one two three", out) - code, out, err = self.t("rc.search.case.sensitive:no ls description.contains:Six") + code, out, err = self.t( + "rc.search.case.sensitive:no ls description.contains:Six" + ) self.assertIn("one two three", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/col.t.cpp b/test/col_test.cpp similarity index 61% rename from test/col.t.cpp rename to test/col_test.cpp index a579d52b8..d6a8f5e1d 100644 --- a/test/col.t.cpp +++ b/test/col_test.cpp @@ -25,19 +25,18 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include +// cmake.h include header must come first + #include -#include #include //////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest test (12); +int TEST_NAME(int, char**) { + UnitTest test(12); // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); + unsetenv("TASKDATA"); + unsetenv("TASKRC"); ColumnID columnID; unsigned int minimum = 0; @@ -45,37 +44,36 @@ int main (int, char**) Task t1; t1.id = 3; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 1, "id:3 --> ColID::measure minimum 1"); - test.is ((int)maximum, 1, "id:3 --> ColID::measure maximum 1"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 1, "id:3 --> ColID::measure minimum 1"); + test.is((int)maximum, 1, "id:3 --> ColID::measure maximum 1"); t1.id = 33; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 2, "id:33 --> ColID::measure minimum 2"); - test.is ((int)maximum, 2, "id:33 --> ColID::measure maximum 2"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 2, "id:33 --> ColID::measure minimum 2"); + test.is((int)maximum, 2, "id:33 --> ColID::measure maximum 2"); t1.id = 333; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 3, "id:333 --> ColID::measure minimum 3"); - test.is ((int)maximum, 3, "id:333 --> ColID::measure maximum 3"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 3, "id:333 --> ColID::measure minimum 3"); + test.is((int)maximum, 3, "id:333 --> ColID::measure maximum 3"); t1.id = 3333; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 4, "id:3333 --> ColID::measure minimum 4"); - test.is ((int)maximum, 4, "id:3333 --> ColID::measure maximum 4"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 4, "id:3333 --> ColID::measure minimum 4"); + test.is((int)maximum, 4, "id:3333 --> ColID::measure maximum 4"); t1.id = 33333; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 5, "id:33333 --> ColID::measure minimum 5"); - test.is ((int)maximum, 5, "id:33333 --> ColID::measure maximum 5"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 5, "id:33333 --> ColID::measure minimum 5"); + test.is((int)maximum, 5, "id:33333 --> ColID::measure maximum 5"); t1.id = 333333; - columnID.measure (t1, minimum, maximum); - test.is ((int)minimum, 6, "id:333333 --> ColID::measure minimum 6"); - test.is ((int)maximum, 6, "id:333333 --> ColID::measure maximum 6"); + columnID.measure(t1, minimum, maximum); + test.is((int)minimum, 6, "id:333333 --> ColID::measure minimum 6"); + test.is((int)maximum, 6, "id:333333 --> ColID::measure maximum 6"); return 0; } //////////////////////////////////////////////////////////////////////////////// - diff --git a/test/color.cmd.t b/test/color.cmd.test.py similarity index 85% rename from test/color.cmd.t rename to test/color.cmd.test.py index 0145d21bf..17e20865e 100755 --- a/test/color.cmd.t +++ b/test/color.cmd.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import platform + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -46,12 +46,12 @@ class TestColorCommand(TestCase): self.t = Task() def test_colors_off(self): - """ Verify 'task colors' shows an error with color:off""" + """Verify 'task colors' shows an error with color:off""" code, out, err = self.t.runError("colors") self.assertIn("Color is currently turned off", out) def test_colors_all(self): - """ Verify 'task colors' shows all colors""" + """Verify 'task colors' shows all colors""" code, out, err = self.t("colors rc._forcecolor:on") self.assertIn("Basic colors", out) self.assertIn("Effects", out) @@ -61,22 +61,24 @@ class TestColorCommand(TestCase): self.assertIn("Try running 'task color white on red'.", out) def test_colors_sample(self): - """ Verify 'task colors red' shows a sample""" + """Verify 'task colors red' shows a sample""" code, out, err = self.t("colors rc._forcecolor:on red") - self.assertRegex(out, "Your sample:\n\n .\[31mtask color red.\[0m") + self.assertRegex(out, "Your sample:\n\n .\\[31mtask color red.\\[0m") def test_colors_legend(self): - """ Verify 'task colors legend' shows theme colors""" + """Verify 'task colors legend' shows theme colors""" code, out, err = self.t("colors rc._forcecolor:on legend") - self.assertRegex(out, "color.debug\s+.\[0m\s.\[38;5;4mcolor4\s+.\[0m") + self.assertRegex(out, r"color.debug\s+.\[0m\s.\[38;5;4mcolor4\s+.\[0m") def test_colors_legend_override(self): """Verify 'task colors legend' obeys rc overrides""" code, out, err = self.t("colors rc._forcecolor:on rc.color.debug:red legend") - self.assertRegex(out, "color.debug\s+.\[0m\s.\[31mred\s+.\[0m") + self.assertRegex(out, r"color.debug\s+.\[0m\s.\[31mred\s+.\[0m") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/color.rules.t b/test/color.rules.t deleted file mode 100755 index f6a52ee21..000000000 --- a/test/color.rules.t +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -############################################################################### -# -# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -# -# 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. -# -# https://www.opensource.org/licenses/mit-license.php -# -############################################################################### - -import sys -import os -import unittest -import platform -# Ensure python finds the local simpletap module -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from basetest import Task, TestCase - - -class TestColorRules(TestCase): - @classmethod - def setUpClass(cls): - """Executed only once, during class setup""" - cls.t = Task() - - # Controlling peripheral color. - cls.t.config('_forcecolor', 'on') - cls.t.config('fontunderline', 'off') # label underlining complicates the tests. - cls.t.config('color.alternate', '') # alternating color complicateѕ the tests. - cls.t.config('default.command', 'list') - cls.t.config('uda.xxx.type', 'numeric') - cls.t.config('uda.xxx.label', 'XXX') - - # Color rules. - cls.t.config('color.active', 'red') - cls.t.config('color.blocked', 'red') - cls.t.config('color.blocking', 'blue') - cls.t.config('color.due', 'red') - cls.t.config('color.overdue', 'blue') - cls.t.config('color.error', 'blue') - cls.t.config('color.header', 'blue') - cls.t.config('color.footnote', 'red') - cls.t.config('color.debug', 'green') - cls.t.config('color.project.x', 'red') - cls.t.config('color.project.none', '') - cls.t.config('color.uda.priority.H', 'red') - cls.t.config('color.uda.priority.M', 'blue') - cls.t.config('color.uda.priority.L', 'green') - cls.t.config('color.keyword.keyword', 'red') - cls.t.config('color.tagged', '') - cls.t.config('color.tag.none', '') - cls.t.config('color.tag.x', 'red') - cls.t.config('color.recurring', 'red') - cls.t.config('color.uda.xxx', 'red') - cls.t.config('color.uda.xxx.4', 'blue') - - cls.t('add control task') # 1 - cls.t('add active task') # 2 - cls.t('2 start') - cls.t('add blocked task') # 3 - cls.t('add blocking task') # 4 - cls.t('3 modify depends:4') - cls.t('add tomorrow due:tomorrow') # 5 - cls.t('add yesterday due:yesterday') # 6 - cls.t('add anhourago due:now-1h') # 7 - cls.t('add someday due:yesterday') # 8 - cls.t('add project_x project:x') # 9 - cls.t('add pri_h priority:H') # 10 - cls.t('add pri_m priority:M') # 11 - cls.t('add pri_l priority:L') # 12 - cls.t('add keyword') # 13 - cls.t('add tag_x +x') # 14 - cls.t('add uda_xxx_1 xxx:1') # 15 - cls.t('add uda_xxx_4 xxx:4') # 16 - cls.t('add recurring due:tomorrow recur:1week') # 17 Keep this last - - def test_control(self): - """No color on control task.""" - code, out, err = self.t('1 info') - self.assertNotIn('\x1b[', out) - - def test_disable_in_pipe(self): - """No color in pipe unless forced.""" - code, out, err = self.t('2 info rc._forcecolor:off') - self.assertNotIn('\x1b[', out) - - def test_active(self): - """Active color rule.""" - code, out, err = self.t('/active/ info') - self.assertIn('\x1b[31m', out) - - def test_blocked(self): - """Blocked color rule.""" - code, out, err = self.t('/blocked/ info') - self.assertIn('\x1b[31m', out) - - def test_blocking(self): - """Blocking color rule.""" - code, out, err = self.t('/blocking/ info') - self.assertIn('\x1b[34m', out) - - def test_due_yesterday(self): - """Overdue color rule.""" - code, out, err = self.t('/yesterday/ info') - self.assertIn('\x1b[34m', out) - - def test_due_anhourago(self): - """Overdue color rule from an hour ago.""" - code, out, err = self.t('/anhourago/ info') - # Match 4-bit or 8-bit blue color code - self.assertRegex(out, '\x1b\[(38;5;4|34)m') - - def test_due_tomorrow(self): - """Due tomorrow color rule.""" - code, out, err = self.t('/tomorrow/ info') - self.assertIn('\x1b[31m', out) - - def test_due_someday(self): - """Due someday color rule.""" - code, out, err = self.t('/someday/ info') - self.assertIn('\x1b[', out) - - def test_color_error(self): - """Error color.""" - code, out, err = self.t.runError('add error priority:X') - self.assertIn('\x1b[34m', err) - - def test_color_header(self): - """Header color.""" - code, out, err = self.t('rc.verbose=header,default /control/') - self.assertIn('\x1b[34m', err) - - def test_color_footnote(self): - """Footnote color.""" - code, out, err = self.t('rc.verbose=on /control/') - self.assertIn('\x1b[31mConfiguration override', err) - - def test_color_debug(self): - """Debug color.""" - code, out, err = self.t('rc.debug=1 /control/') - self.assertIn('\x1b[32mTimer', err) - - def test_project_x(self): - """Project x color rule.""" - code, out, err = self.t('/project_x/ info') - self.assertIn('\x1b[31m', out) - - def test_project_none(self): - """Project none color rule.""" - code, out, err = self.t('/control/ rc.color.project.none=red info') - self.assertIn('\x1b[31m', out) - - def test_priority_h(self): - """Priority H color rule.""" - code, out, err = self.t('/pri_h/ info') - self.assertIn('\x1b[31m', out) - - def test_priority_m(self): - """Priority M color rule.""" - code, out, err = self.t('/pri_m/ info') - self.assertIn('\x1b[34m', out) - - def test_priority_l(self): - """Priority L color rule.""" - code, out, err = self.t('/pri_l/ info') - self.assertIn('\x1b[32m', out) - - def test_keyword(self): - """Keyword color rule.""" - code, out, err = self.t('/keyword/ info') - self.assertIn('\x1b[31m', out) - - def test_tag_x(self): - """Tag x color rule.""" - code, out, err = self.t('/tag_x/ info') - self.assertIn('\x1b[31m', out) - - def test_tag_none(self): - """Tag none color rule.""" - code, out, err = self.t('/control/ rc.color.tag.none=red info') - self.assertIn('\x1b[31m', out) - - def test_tagged(self): - """Tagged color rule.""" - code, out, err = self.t('/tag_x/ rc.color.tag.x= rc.color.tagged=blue info') - self.assertIn('\x1b[34m', out) - - def test_recurring(self): - """Recurring color rule.""" - code, out, err = self.t('/recurring/ info') - self.assertIn('\x1b[31m', out) - - def test_uda(self): - """UDA color rule.""" - code, out, err = self.t('/uda_xxx_1/ info') - self.assertIn('\x1b[31m', out) - - def test_uda_value(self): - """UDA Value color rule.""" - code, out, err = self.t('/uda_xxx_4/ rc.color.uda.xxx= info') - self.assertIn('\x1b[34m', out) - -class TestColorRulesMerging(TestCase): - - def setUp(self): - """Executed before every test in the class""" - self.t = Task() - - # Controlling peripheral color. - self.t.config('_forcecolor', 'on') - self.t.config('fontunderline', 'off') # label underlining complicates the tests. - self.t.config('color.alternate', '') # alternating color complicateѕ the tests. - self.t.config('default.command', 'list') - - # Color rules. Only due and tagged affect resulting color. - self.t.config('color.due', 'red') - self.t.config('color.tagged','on white') - self.t.config('rule.color.precedence', 'due,tagged') - - self.t('add due:tomorrow +home hometask') # Task that matches both color rules - - @unittest.skipIf('CYGWIN' in platform.system(), 'Skipping color merge test for Cygwin') - @unittest.skipIf('FreeBSD' in platform.system(), 'Skipping color merge test for FREEBSD') - def test_colors_merge(self): - """Tests whether colors merge""" - code, out, err = self.t('1 info') - self.assertIn('\x1b[31;47mhometask', out) # Red on white - - @unittest.skipIf('CYGWIN' in platform.system(), 'Skipping color merge test for Cygwin') - @unittest.skipIf('FreeBSD' in platform.system(), 'Skipping color merge test for FREEBSD') - def test_colors_merge_off(self): - """No color merge behaviour with rule.color.merge=no""" - self.t.config('rule.color.merge', 'no') - - code, out, err = self.t('1 info') - self.assertIn('\x1b[31mhometask', out) # Red - - -if __name__ == "__main__": - from simpletap import TAPTestRunner - unittest.main(testRunner=TAPTestRunner()) - -# vim: ai sts=4 et sw=4 ft=python diff --git a/test/color.rules.test.py b/test/color.rules.test.py new file mode 100755 index 000000000..422ae25a1 --- /dev/null +++ b/test/color.rules.test.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import unittest +import platform + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestColorRules(TestCase): + @classmethod + def setUpClass(cls): + """Executed only once, during class setup""" + cls.t = Task() + + # Controlling peripheral color. + cls.t.config("_forcecolor", "on") + cls.t.config("fontunderline", "off") # label underlining complicates the tests. + cls.t.config("color.alternate", "") # alternating color complicateѕ the tests. + cls.t.config("default.command", "list") + cls.t.config("uda.xxx.type", "numeric") + cls.t.config("uda.xxx.label", "XXX") + + # Color rules. + cls.t.config("color.active", "red") + cls.t.config("color.blocked", "red") + cls.t.config("color.blocking", "blue") + cls.t.config("color.due", "red") + cls.t.config("color.overdue", "blue") + cls.t.config("color.error", "blue") + cls.t.config("color.header", "blue") + cls.t.config("color.footnote", "red") + cls.t.config("color.debug", "green") + cls.t.config("color.project.x", "red") + cls.t.config("color.project.none", "") + cls.t.config("color.uda.priority.H", "red") + cls.t.config("color.uda.priority.M", "blue") + cls.t.config("color.uda.priority.L", "green") + cls.t.config("color.keyword.keyword", "red") + cls.t.config("color.tagged", "") + cls.t.config("color.tag.none", "") + cls.t.config("color.tag.x", "red") + cls.t.config("color.recurring", "red") + cls.t.config("color.uda.xxx", "red") + cls.t.config("color.uda.xxx.4", "blue") + + cls.t("add control task") # 1 + cls.t("add active task") # 2 + cls.t("2 start") + cls.t("add blocked task") # 3 + cls.t("add blocking task") # 4 + cls.t("3 modify depends:4") + cls.t("add tomorrow due:tomorrow") # 5 + cls.t("add yesterday due:yesterday") # 6 + cls.t("add anhourago due:now-1h") # 7 + cls.t("add someday due:yesterday") # 8 + cls.t("add project_x project:x") # 9 + cls.t("add pri_h priority:H") # 10 + cls.t("add pri_m priority:M") # 11 + cls.t("add pri_l priority:L") # 12 + cls.t("add keyword") # 13 + cls.t("add tag_x +x") # 14 + cls.t("add uda_xxx_1 xxx:1") # 15 + cls.t("add uda_xxx_4 xxx:4") # 16 + cls.t("add recurring due:tomorrow recur:1week") # 17 Keep this last + + def test_control(self): + """No color on control task.""" + code, out, err = self.t("1 info") + self.assertNotIn("\x1b[", out) + + def test_disable_in_pipe(self): + """No color in pipe unless forced.""" + code, out, err = self.t("2 info rc._forcecolor:off") + self.assertNotIn("\x1b[", out) + + def test_active(self): + """Active color rule.""" + code, out, err = self.t("/active/ info") + self.assertIn("\x1b[31m", out) + + def test_blocked(self): + """Blocked color rule.""" + code, out, err = self.t("/blocked/ info") + self.assertIn("\x1b[31m", out) + + def test_blocking(self): + """Blocking color rule.""" + code, out, err = self.t("/blocking/ info") + self.assertIn("\x1b[34m", out) + + def test_due_yesterday(self): + """Overdue color rule.""" + code, out, err = self.t("/yesterday/ info") + self.assertIn("\x1b[34m", out) + + def test_due_anhourago(self): + """Overdue color rule from an hour ago.""" + code, out, err = self.t("/anhourago/ info") + # Match 4-bit or 8-bit blue color code + self.assertRegex(out, "\x1b\\[(38;5;4|34)m") + + def test_due_tomorrow(self): + """Due tomorrow color rule.""" + code, out, err = self.t("/tomorrow/ info") + self.assertIn("\x1b[31m", out) + + def test_due_someday(self): + """Due someday color rule.""" + code, out, err = self.t("/someday/ info") + self.assertIn("\x1b[", out) + + def test_color_error(self): + """Error color.""" + code, out, err = self.t.runError("add error priority:X") + self.assertIn("\x1b[34m", err) + + def test_color_header(self): + """Header color.""" + code, out, err = self.t("rc.verbose=header,default /control/") + self.assertIn("\x1b[34m", err) + + def test_color_footnote(self): + """Footnote color.""" + code, out, err = self.t("rc.verbose=on /control/") + self.assertIn("\x1b[31mConfiguration override", err) + + def test_color_debug(self): + """Debug color.""" + code, out, err = self.t("rc.debug=1 /control/") + self.assertIn("\x1b[32mTimer", err) + + def test_project_x(self): + """Project x color rule.""" + code, out, err = self.t("/project_x/ info") + self.assertIn("\x1b[31m", out) + + def test_project_none(self): + """Project none color rule.""" + code, out, err = self.t("/control/ rc.color.project.none=red info") + self.assertIn("\x1b[31m", out) + + def test_priority_h(self): + """Priority H color rule.""" + code, out, err = self.t("/pri_h/ info") + self.assertIn("\x1b[31m", out) + + def test_priority_m(self): + """Priority M color rule.""" + code, out, err = self.t("/pri_m/ info") + self.assertIn("\x1b[34m", out) + + def test_priority_l(self): + """Priority L color rule.""" + code, out, err = self.t("/pri_l/ info") + self.assertIn("\x1b[32m", out) + + def test_keyword(self): + """Keyword color rule.""" + code, out, err = self.t("/keyword/ info") + self.assertIn("\x1b[31m", out) + + def test_tag_x(self): + """Tag x color rule.""" + code, out, err = self.t("/tag_x/ info") + self.assertIn("\x1b[31m", out) + + def test_tag_none(self): + """Tag none color rule.""" + code, out, err = self.t("/control/ rc.color.tag.none=red info") + self.assertIn("\x1b[31m", out) + + def test_tagged(self): + """Tagged color rule.""" + code, out, err = self.t("/tag_x/ rc.color.tag.x= rc.color.tagged=blue info") + self.assertIn("\x1b[34m", out) + + def test_recurring(self): + """Recurring color rule.""" + code, out, err = self.t("/recurring/ info") + self.assertIn("\x1b[31m", out) + + def test_uda(self): + """UDA color rule.""" + code, out, err = self.t("/uda_xxx_1/ info") + self.assertIn("\x1b[31m", out) + + def test_uda_value(self): + """UDA Value color rule.""" + code, out, err = self.t("/uda_xxx_4/ rc.color.uda.xxx= info") + self.assertIn("\x1b[34m", out) + + +class TestColorRulesMerging(TestCase): + + def setUp(self): + """Executed before every test in the class""" + self.t = Task() + + # Controlling peripheral color. + self.t.config("_forcecolor", "on") + self.t.config( + "fontunderline", "off" + ) # label underlining complicates the tests. + self.t.config("color.alternate", "") # alternating color complicateѕ the tests. + self.t.config("default.command", "list") + + # Color rules. Only due and tagged affect resulting color. + self.t.config("color.due", "red") + self.t.config("color.tagged", "on white") + self.t.config("rule.color.precedence", "due,tagged") + + self.t("add due:tomorrow +home hometask") # Task that matches both color rules + + @unittest.skipIf( + "CYGWIN" in platform.system(), "Skipping color merge test for Cygwin" + ) + @unittest.skipIf( + "FreeBSD" in platform.system(), "Skipping color merge test for FREEBSD" + ) + def test_colors_merge(self): + """Tests whether colors merge""" + code, out, err = self.t("1 info") + self.assertIn("\x1b[31;47mhometask", out) # Red on white + + @unittest.skipIf( + "CYGWIN" in platform.system(), "Skipping color merge test for Cygwin" + ) + @unittest.skipIf( + "FreeBSD" in platform.system(), "Skipping color merge test for FREEBSD" + ) + def test_colors_merge_off(self): + """No color merge behaviour with rule.color.merge=no""" + self.t.config("rule.color.merge", "no") + + code, out, err = self.t("1 info") + self.assertIn("\x1b[31mhometask", out) # Red + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/columns.t b/test/columns.test.py similarity index 68% rename from test/columns.t rename to test/columns.test.py index 4128f9459..7f14c571e 100755 --- a/test/columns.t +++ b/test/columns.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,7 +41,7 @@ class TestDescriptionFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,description") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add zero") cls.t("add one long description to exceed a certain string size") @@ -66,13 +66,18 @@ class TestDescriptionFormats(TestCase): def test_description_oneline(self): """Verify formatting of 'description.oneline' column""" code, out, err = self.t("xxx rc.report.xxx.columns:id,description.oneline") - self.assertRegex(out, r"one long description to exceed a certain string size \d{4}-\d{2}-\d{2}") + self.assertRegex( + out, + r"one long description to exceed a certain string size \d{4}-\d{2}-\d{2}", + ) self.assertIn("annotation", out) self.assertNotIn("[1]", out) def test_description_truncated(self): """Verify formatting of 'description.truncated' column""" - code, out, err = self.t("xxx rc.detection:off rc.defaultwidth:40 rc.report.xxx.columns:id,description.truncated") + code, out, err = self.t( + "xxx rc.detection:off rc.defaultwidth:40 rc.report.xxx.columns:id,description.truncated" + ) self.assertIn("exceed a c...", out) self.assertNotIn("annotation", out) self.assertNotIn("[1]", out) @@ -85,13 +90,17 @@ class TestDescriptionFormats(TestCase): def test_description_truncated_count(self): """Verify formatting of 'description.truncated_count' column""" - code, out, err = self.t("xxx rc.detection:off rc.defaultwidth:40 rc.report.xxx.columns:id,description.truncated_count") + code, out, err = self.t( + "xxx rc.detection:off rc.defaultwidth:40 rc.report.xxx.columns:id,description.truncated_count" + ) self.assertIn("exceed... [1]", out) self.assertNotIn("annotation", out) def test_description_format_unrecognized(self): """Verify descriptionuuid.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,description.donkey") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,description.donkey" + ) self.assertEqual(err, "Unrecognized column format 'description.donkey'\n") @@ -100,29 +109,26 @@ class TestUUIDFormats(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("report.xxx.columns", "id,uuid") - cls.t.config("verbose", "nothing") + cls.t.config("report.xxx.columns", "uuid") + cls.t.config("verbose", "nothing") cls.t("add zero") code, out, err = cls.t("_get 1.uuid") cls.uuid = out.strip() - def setUp(self): - """Executed before each test in the class""" - def test_uuid_long(self): """Verify formatting of 'uuid.long' column""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,uuid.long") - self.assertIn(self.uuid, out) + code, out, err = self.t("xxx rc.report.xxx.columns:uuid.long") + self.assertEqual(self.uuid, out.strip()) def test_uuid_short(self): """Verify formatting of 'uuid.short' column""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,uuid.short") - self.assertIn(self.uuid[:7], out) + code, out, err = self.t("xxx rc.report.xxx.columns:uuid.short") + self.assertEqual(self.uuid[:8], out.strip()) def test_uuid_format_unrecognized(self): """Verify uuid.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,uuid.donkey") + code, out, err = self.t.runError("xxx rc.report.xxx.columns:uuid.donkey") self.assertEqual(err, "Unrecognized column format 'uuid.donkey'\n") @@ -132,7 +138,7 @@ class TestUrgencyFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,urgency") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add one project:A due:yesterday +tag") @@ -162,7 +168,7 @@ class TestIDFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add zero") @@ -187,7 +193,7 @@ class TestStatusFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,status") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add zero") cls.t("add one") @@ -232,7 +238,7 @@ class TestRecurringAttributeFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add one due:eoy recur:monthly") cls.t("list") @@ -242,22 +248,34 @@ class TestRecurringAttributeFormats(TestCase): def test_recurrence_formats_short(self): """Verify formatting of assorted short recurrence columns""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,status,due,recur.indicator,mask,imask,parent.short") - self.assertRegex(out, "1\sRecurring\s+\d{4}-\d{2}-\d{2}\s+R\s+-") - self.assertRegex(out, "2\sPending\s+\d{4}-\d{2}-\d{2}\s+R\s+0\s+[0-9a-fA-F]{8}") + code, out, err = self.t( + "xxx rc.report.xxx.columns:id,status,due,recur.indicator,mask,imask,parent.short" + ) + self.assertRegex(out, r"1\sRecurring\s+\d{4}-\d{2}-\d{2}\s+R\s+-") + self.assertRegex( + out, r"2\sPending\s+\d{4}-\d{2}-\d{2}\s+R\s+0\s+[0-9a-fA-F]{8}" + ) def test_recurrence_formats_long(self): """Verify formatting of assorted long recurrence columns""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,status,due,recur.duration,mask,imask,parent.long") - self.assertRegex(out, "1\sRecurring\s+\d{4}-\d{2}-\d{2}\s+P30D\s+-") - self.assertRegex(out, "2\sPending\s+\d{4}-\d{2}-\d{2}\s+P30D\s+0\s+[0-9a-fA-F-]{36}") + code, out, err = self.t( + "xxx rc.report.xxx.columns:id,status,due,recur.duration,mask,imask,parent.long" + ) + self.assertRegex(out, r"1\sRecurring\s+\d{4}-\d{2}-\d{2}\s+P30D\s+-") + self.assertRegex( + out, r"2\sPending\s+\d{4}-\d{2}-\d{2}\s+P30D\s+0\s+[0-9a-fA-F-]{36}" + ) def test_recurrence_format_unrecognized(self): """Verify *.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,status,due,recur.donkey,mask,imask,parent.long") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,status,due,recur.donkey,mask,imask,parent.long" + ) self.assertEqual(err, "Unrecognized column format 'recur.donkey'\n") - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,status,due,recur.duration,mask,imask,parent.donkey") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,status,due,recur.duration,mask,imask,parent.donkey" + ) self.assertEqual(err, "Unrecognized column format 'parent.donkey'\n") @@ -267,7 +285,7 @@ class TestProjectFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,project,description") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add one project:TOP") cls.t("add two project:TOP.MIDDLE") @@ -279,27 +297,33 @@ class TestProjectFormats(TestCase): def test_project_format_full(self): """Verify project.full formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,project.full,description") - self.assertRegex(out, r'1\s+TOP\s+one') - self.assertRegex(out, r'2\s+TOP.MIDDLE\s+two') - self.assertRegex(out, r'3\s+TOP.MIDDLE.BOTTOM\s+three') + self.assertRegex(out, r"1\s+TOP\s+one") + self.assertRegex(out, r"2\s+TOP.MIDDLE\s+two") + self.assertRegex(out, r"3\s+TOP.MIDDLE.BOTTOM\s+three") def test_project_format_parent(self): """Verify project.parent formatting""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,project.parent,description") - self.assertRegex(out, r'1\s+TOP\s+one') - self.assertRegex(out, r'2\s+TOP\s+two') - self.assertRegex(out, r'3\s+TOP\s+three') + code, out, err = self.t( + "xxx rc.report.xxx.columns:id,project.parent,description" + ) + self.assertRegex(out, r"1\s+TOP\s+one") + self.assertRegex(out, r"2\s+TOP\s+two") + self.assertRegex(out, r"3\s+TOP\s+three") def test_project_format_indented(self): """Verify project.indented formatting""" - code, out, err = self.t("xxx rc.report.xxx.columns:id,project.indented,description") - self.assertRegex(out, r'1\s+TOP\s+one') - self.assertRegex(out, r'2\s+MIDDLE\s+two') - self.assertRegex(out, r'3\s+BOTTOM\s+three') + code, out, err = self.t( + "xxx rc.report.xxx.columns:id,project.indented,description" + ) + self.assertRegex(out, r"1\s+TOP\s+one") + self.assertRegex(out, r"2\s+MIDDLE\s+two") + self.assertRegex(out, r"3\s+BOTTOM\s+three") def test_project_format_unrecognized(self): """Verify project.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,project.donkey,description") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,project.donkey,description" + ) self.assertEqual(err, "Unrecognized column format 'project.donkey'\n") @@ -309,28 +333,30 @@ class TestTagsFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,tags,description") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add one +tag1 +tag2") def test_tags_format_list(self): """Verify tags.list formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,tags.list") - self.assertRegex(out, r'1\s+tag1\stag2$') + self.assertRegex(out, r"1\s+tag1\stag2$") def test_tags_format_indicator(self): """Verify tags.indicator formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,tags.indicator") - self.assertRegex(out, r'1\s+\+$') + self.assertRegex(out, r"1\s+\+$") def test_tags_format_count(self): """Verify tags.count formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,tags.count") - self.assertRegex(out, r'1\s+\[2\]$') + self.assertRegex(out, r"1\s+\[2\]$") def test_tags_format_unrecognized(self): """Verify tags.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,tags.donkey,description") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,tags.donkey,description" + ) self.assertEqual(err, "Unrecognized column format 'tags.donkey'\n") @@ -340,7 +366,7 @@ class TestDateFormats(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,due") - cls.t.config("verbose", "nothing") + cls.t.config("verbose", "nothing") cls.t("add one due:yesterday") cls.t("add two due:tomorrow") @@ -348,54 +374,56 @@ class TestDateFormats(TestCase): def test_date_format_formatted(self): """Verify due.formatted formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.formatted") - self.assertRegex(out, r'1\s+\d{4}-\d{2}-\d{2}') - self.assertRegex(out, r'2\s+\d{4}-\d{2}-\d{2}') + self.assertRegex(out, r"1\s+\d{4}-\d{2}-\d{2}") + self.assertRegex(out, r"2\s+\d{4}-\d{2}-\d{2}") def test_date_format_julian(self): """Verify due.julian formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.julian") - self.assertRegex(out, r'1\s+\d+\.\d+') - self.assertRegex(out, r'2\s+\d+\.\d+') + self.assertRegex(out, r"1\s+\d+\.\d+") + self.assertRegex(out, r"2\s+\d+\.\d+") def test_date_format_epoch(self): """Verify due.epoch formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.epoch") - self.assertRegex(out, r'1\s+\d{10}') - self.assertRegex(out, r'2\s+\d{10}') + self.assertRegex(out, r"1\s+\d{10}") + self.assertRegex(out, r"2\s+\d{10}") def test_date_format_iso(self): """Verify due.iso formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.iso") - self.assertRegex(out, r'1\s+\d{8}T\d{6}Z') - self.assertRegex(out, r'2\s+\d{8}T\d{6}Z') + self.assertRegex(out, r"1\s+\d{8}T\d{6}Z") + self.assertRegex(out, r"2\s+\d{8}T\d{6}Z") def test_date_format_age(self): """Verify due.age formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.age") - self.assertRegex(out, r'1\s+[0-9.]+d') - self.assertRegex(out, r'2\s+-[0-9.]+[hmin]+') + self.assertRegex(out, r"1\s+[0-9.]+d") + self.assertRegex(out, r"2\s+-[0-9.]+[hmin]+") def test_date_format_remaining(self): """Verify due.remaining formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.remaining") - self.assertRegex(out, r'1') - self.assertRegex(out, r'2\s+\d+\S+') + self.assertRegex(out, r"1") + self.assertRegex(out, r"2\s+\d+\S+") def test_date_format_relative(self): """Verify due.relative formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.relative") - self.assertRegex(out, r'1\s+-[0-9.]+d') - self.assertRegex(out, r'2\s+[0-9.]+[hmin]+') + self.assertRegex(out, r"1\s+-[0-9.]+d") + self.assertRegex(out, r"2\s+[0-9.]+[hmin]+") def test_date_format_countdown(self): """Verify due.countdown formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,due.countdown") - self.assertRegex(out, r'1\s+') - self.assertRegex(out, r'2\s+\d+\S+') + self.assertRegex(out, r"1\s+") + self.assertRegex(out, r"2\s+\d+\S+") def test_date_format_unrecognized(self): """Verify due.donkey formatting fails""" - code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,due.donkey,description") + code, out, err = self.t.runError( + "xxx rc.report.xxx.columns:id,due.donkey,description" + ) self.assertEqual(err, "Unrecognized column format 'due.donkey'\n") @@ -407,9 +435,9 @@ class TestCustomColumns(TestCase): def test_unrecognized_column(self): """verify that using a bogus colum generates an error""" self.t.config("report.foo.description", "DESC") - self.t.config("report.foo.columns", "id,foo,description") - self.t.config("report.foo.sort", "id+") - self.t.config("report.foo.filter", "project:A") + self.t.config("report.foo.columns", "id,foo,description") + self.t.config("report.foo.sort", "id+") + self.t.config("report.foo.filter", "project:A") # Generate the usage screen, and locate the custom report on it. code, out, err = self.t.runError("foo") @@ -421,8 +449,8 @@ class TestUDAFormats(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("report.xxx.columns", "id,priority") - cls.t.config("verbose", "nothing") + cls.t.config("report.xxx.columns", "id,priority") + cls.t.config("verbose", "nothing") cls.t.config("uda.priority.indicator", "P") cls.t("add one priority:H") @@ -430,19 +458,18 @@ class TestUDAFormats(TestCase): def test_uda_format_formatted(self): """Verify priority.default formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,priority.default") - self.assertRegex(out, r'1\s+H') + self.assertRegex(out, r"1\s+H") def test_uda_format_indicator(self): """Verify priority.indicator formatting""" code, out, err = self.t("xxx rc.report.xxx.columns:id,priority.indicator") - self.assertRegex(out, r'1\s+P') + self.assertRegex(out, r"1\s+P") def test_uda_format_unrecognized(self): """Verify priority.donkey formatting fails""" code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,priority.donkey") self.assertEqual(err, "Unrecognized column format 'priority.donkey'\n") - """ depends list* 1 2 10 count [3] @@ -451,6 +478,71 @@ depends list* 1 2 10 start active* ✓ """ + +class TestUDAUUIDFormats(TestCase): + @classmethod + def setUpClass(cls): + """Executed once before any test in the class""" + cls.t = Task() + cls.t.config("verbose", "nothing") + cls.t.config("uda.uda_uuid.label", "uda_uuid") + cls.t.config("uda.uda_uuid.type", "uuid") + cls.t.config("report.xxx.columns", "uda_uuid") + + cls.t("add zero") + code, out, err = cls.t("_get 1.uuid") + cls.t("add uda_uuid:{} one".format(out.strip())) + code, out, err = cls.t("_get 2.uda_uuid") + cls.uda_uuid = out.strip() + + def test_uda_uuid_invalid_fails(self): + """Verify adding invalid uuid fails""" + code, out, err = self.t.runError("add uda_uuid:shrek three") + self.assertNotEqual(code, 0) + self.assertIn("uda_uuid", err.strip()) + self.assertIn("shrek", err.strip()) + + def test_uda_uuid_long(self): + """Verify formatting of 'uda_uuid.long' column""" + code, out, err = self.t("2 xxx rc.report.xxx.columns:uda_uuid.long") + self.assertEqual(self.uda_uuid, out.strip()) + + def test_uda_uuid_short(self): + """Verify formatting of 'uda_uuid.short' column""" + code, out, err = self.t("2 xxx rc.report.xxx.columns:uda_uuid.short") + self.assertEqual(self.uda_uuid[:8], out.strip()) + + def test_uda_uuid_format_unrecognized(self): + """Verify uda_uuid.donkey formatting fails""" + code, out, err = self.t.runError("xxx rc.report.xxx.columns:id,uda_uuid.donkey") + self.assertEqual(err, "Unrecognized column format 'uda_uuid.donkey'\n") + + +class TestUDAUUIDReconfiguredFromString(TestCase): + @classmethod + def setUpClass(cls): + """Executed once before any test in the class""" + cls.t = Task() + cls.t.config("verbose", "nothing") + cls.t.config("uda.uda_uuid.label", "uda_uuid") + cls.t.config("report.xxx.columns", "uda_uuid") + + cls.t.config("uda.uda_uuid.type", "string") + cls.expected_str = 3 * "littlepigs" + cls.t("add uda_uuid:{} one".format(cls.expected_str)) + cls.t.config("uda.uda_uuid.type", "uuid") + + def test_uda_uuid_long(self): + """Verify formatting of 'uda_uuid.long' column""" + code, out, err = self.t("1 xxx rc.report.xxx.columns:uda_uuid.long") + self.assertEqual(self.expected_str, out.strip()) + + def test_uda_uuid_short(self): + """Verify formatting of 'uda_uuid.short' column""" + code, out, err = self.t("1 xxx rc.report.xxx.columns:uda_uuid.short") + self.assertEqual(self.expected_str[:8], out.strip()) + + class TestFeature1061(TestCase): def setUp(self): """Executed before each test in the class""" @@ -480,6 +572,7 @@ class TestFeature1061(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/commands.t b/test/commands.test.py similarity index 77% rename from test/commands.t rename to test/commands.test.py index f59bf6ec9..7ee9e38a3 100755 --- a/test/commands.t +++ b/test/commands.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,20 +42,25 @@ class TestCommands(TestCase): def test_command_dna(self): """Verify 'add', 'modify', 'list' dna""" code, out, err = self.t("commands") - self.assertRegex(out, "add\s+operation\s+RW\s+Ctxt\s+Mods\s+Adds a new task") - self.assertRegex(out, "list\s+report\s+RO\s+ID\s+GC\s+Ctxt\s+Filt\s+Most details of") - self.assertRegex(out, "modify\s+operation\s+RW\s+Filt\s+Mods\s+Modifies the") + self.assertRegex(out, r"add\s+operation\s+RW\s+Ctxt\s+Mods\s+Adds a new task") + self.assertRegex( + out, r"list\s+report\s+RO\s+ID\s+GC\s+Recur\s+Ctxt\s+Filt\s+Most details of" + ) + self.assertRegex(out, r"modify\s+operation\s+RW\s+Filt\s+Mods\s+Modifies the") def test_command_dna_color(self): """Verify 'add', 'modify', 'list' dna""" code, out, err = self.t("commands rc._forcecolor:on") - self.assertRegex(out, "add\s+operation\s+RW\s+Ctxt\s+Mods\s+Adds a new task") - self.assertRegex(out, "list\s+report\s+RO\s+ID\s+GC\s+Ctxt\s+Filt\s+Most details of") - self.assertRegex(out, "modify\s+operation\s+RW\s+Filt\s+Mods\s+Modifies the") + self.assertRegex(out, r"add\s+operation\s+RW\s+Ctxt\s+Mods\s+Adds a new task") + self.assertRegex( + out, r"list\s+report\s+RO\s+ID\s+GC\s+Recur\s+Ctxt\s+Filt\s+Most details of" + ) + self.assertRegex(out, r"modify\s+operation\s+RW\s+Filt\s+Mods\s+Modifies the") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/completed.t b/test/completed.test.py similarity index 94% rename from test/completed.t rename to test/completed.test.py index fb0787833..91f612808 100755 --- a/test/completed.t +++ b/test/completed.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -50,13 +50,14 @@ class TestCompleted(TestCase): self.t("2 delete", input="y\n") code, out, err = self.t("completed") - self.assertIn('one', out) - self.assertNotIn('two', out) - self.assertNotIn('three', out) + self.assertIn("one", out) + self.assertNotIn("two", out) + self.assertNotIn("three", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/configuration.t b/test/configuration.test.py similarity index 96% rename from test/configuration.t rename to test/configuration.test.py index b9b0b5e33..e4a89e9ce 100755 --- a/test/configuration.t +++ b/test/configuration.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -59,8 +59,8 @@ class TestConfiguration(TestCase): def test_config_completion(self): """verify that the '_config' command generates a full list""" code, out, err = self.t("_config") - self.assertIn("_forcecolor", out) # first - self.assertIn("xterm.title", out) # last + self.assertIn("_forcecolor", out) # first + self.assertIn("xterm.title", out) # last def test_config_nothing(self): """Verify error handling with no args""" @@ -95,6 +95,7 @@ class TestBug1475(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/confirmation.t b/test/confirmation.test.py similarity index 95% rename from test/confirmation.t rename to test/confirmation.test.py index b59777216..bf2d23c60 100755 --- a/test/confirmation.t +++ b/test/confirmation.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -45,7 +45,7 @@ class TestConfirmation(TestCase): self.t.config("confirmation", "on") # Create some playthings. - for id in range(1,11): + for id in range(1, 11): self.t("add foo") # Test the various forms of "Yes". @@ -88,7 +88,9 @@ class TestBug1438(TestCase): code, out, err = self.t("list") self.assertIn("Sometimes", out) - code, out, err = self.t("rc.confirmation=off rc.recurrence.confirmation=off 2 mod /Sometimes/Everytime/") + code, out, err = self.t( + "rc.confirmation=off rc.recurrence.confirmation=off 2 mod /Sometimes/Everytime/" + ) self.assertIn("Modified 1 task", out) code, out, err = self.t("list") self.assertIn("Everytime", out) @@ -96,6 +98,7 @@ class TestBug1438(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/context.t b/test/context.test.py similarity index 71% rename from test/context.t rename to test/context.test.py index cd9234596..db5405394 100755 --- a/test/context.t +++ b/test/context.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -45,186 +44,205 @@ class ContextManagementTest(TestCase): """With confirmation active, prompt if context filter matches no tasks""" self.t.config("confirmation", "on") - code, out, err = self.t.runError('context define work project:Work', input="y\nn\n") + code, out, err = self.t.runError( + "context define work project:Work", input="y\nn\n" + ) self.assertIn("The filter 'project:Work' matches 0 pending tasks.", out) self.assertNotIn("Context 'work' defined", out) # Assert the config contains context definition - self.assertNotIn('context.work=project:Work\n', self.t.taskrc_content) + self.assertNotIn("context.work=project:Work\n", self.t.taskrc_content) def test_context_define(self): """Test simple context definition.""" - code, out, err = self.t('context define work project:Work', input="y\ny\nn\n") + code, out, err = self.t("context define work project:Work", input="y\ny\nn\n") self.assertIn("Context 'work' defined", out) # Assert the config contains read context definition - context_line = 'context.work.read=project:Work\n' + context_line = "context.work.read=project:Work\n" self.assertIn(context_line, self.t.taskrc_content) # Assert that it contains the definition only once self.assertEqual(self.t.taskrc_content.count(context_line), 1) # Assert the config does not contain write context definition - context_line = 'context.work.write=project:Work\n' + context_line = "context.work.write=project:Work\n" self.assertNotIn(context_line, self.t.taskrc_content) # Assert that legacy style was not used # Assert the config contains read context definition - context_line = 'context.work=project:Work\n' + context_line = "context.work=project:Work\n" self.assertNotIn(context_line, self.t.taskrc_content) def test_context_redefine_same_definition(self): """Test re-defining the context with the same definition.""" - self.t('context define work project:Work', input='y\ny\ny\n') - code, out, err = self.t('context define work project:Work', input='y\ny\ny\n') + self.t("context define work project:Work", input="y\ny\ny\n") + code, out, err = self.t("context define work project:Work", input="y\ny\ny\n") self.assertIn("Context 'work' defined (read, write).", out) # Assert the config contains context definition - context_line = 'context.work.read=project:Work\n' + context_line = "context.work.read=project:Work\n" self.assertIn(context_line, self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count(context_line), 1) - context_line = 'context.work.write=project:Work\n' + context_line = "context.work.write=project:Work\n" self.assertIn(context_line, self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count(context_line), 1) def test_context_redefine_different_definition(self): """Test re-defining the context with different definition.""" - self.t('context define work project:Work', input='y\ny\ny\n') - code, out, err = self.t('context define work +work', input='y\ny\ny\n') + self.t("context define work project:Work", input="y\ny\ny\n") + code, out, err = self.t("context define work +work", input="y\ny\ny\n") self.assertIn("Context 'work' defined", out) # Assert the config does not contain the old context definition - self.assertNotIn('context.work.read=project:Work\n', self.t.taskrc_content) - self.assertNotIn('context.work.write=project:Work\n', self.t.taskrc_content) + self.assertNotIn("context.work.read=project:Work\n", self.t.taskrc_content) + self.assertNotIn("context.work.write=project:Work\n", self.t.taskrc_content) # Assert the config contains context definition - context_line = 'context.work.read=+work\n' + context_line = "context.work.read=+work\n" self.assertIn(context_line, self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count(context_line), 1) - context_line = 'context.work.write=+work\n' + context_line = "context.work.write=+work\n" self.assertIn(context_line, self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count(context_line), 1) def test_context_define_invalid_for_write_due_to_modifier(self): """Test definition of a context that is not a valid write context.""" self.t.config("confirmation", "off") - code, out, err = self.t('context define urgent due.before:today') + code, out, err = self.t("context define urgent due.before:today") self.assertIn("Context 'urgent' defined", out) # Assert the config contains read context definition - context_line = 'context.urgent.read=due.before:today\n' + context_line = "context.urgent.read=due.before:today\n" self.assertIn(context_line, self.t.taskrc_content) # Assert that it contains the definition only once self.assertEqual(self.t.taskrc_content.count(context_line), 1) # Assert the config does not contain write context definition - context_line = 'context.urgent.write=due.before:today\n' + context_line = "context.urgent.write=due.before:today\n" self.assertNotIn(context_line, self.t.taskrc_content) # Assert that the write context was not set at all - self.assertNotIn('context.urgent.write=', self.t.taskrc_content) + self.assertNotIn("context.urgent.write=", self.t.taskrc_content) # Assert that legacy style was not used # Assert the config contains read context definition - context_line = 'context.urgent=due.before:today\n' + context_line = "context.urgent=due.before:today\n" self.assertNotIn(context_line, self.t.taskrc_content) def test_context_define_invalid_for_write_due_to_operator(self): """Test definition of a context that is not a valid write context because it uses an OR operator.""" self.t.config("confirmation", "off") - code, out, err = self.t('context define urgent due:today or +next') + code, out, err = self.t("context define urgent due:today or +next") self.assertIn("Context 'urgent' defined", out) # Assert the config contains read context definition - context_line = 'context.urgent.read=due:today or +next\n' + context_line = "context.urgent.read=due:today or +next\n" self.assertIn(context_line, self.t.taskrc_content) # Assert that it contains the definition only once self.assertEqual(self.t.taskrc_content.count(context_line), 1) # Assert the config does not contain write context definition - context_line = 'context.urgent.write=due:today or +next\n' + context_line = "context.urgent.write=due:today or +next\n" self.assertNotIn(context_line, self.t.taskrc_content) # Assert that the write context was not set at all - self.assertNotIn('context.urgent.write=', self.t.taskrc_content) + self.assertNotIn("context.urgent.write=", self.t.taskrc_content) # Assert that legacy style was not used # Assert the config contains read context definition - context_line = 'context.urgent=due:today or +next\n' + context_line = "context.urgent=due:today or +next\n" self.assertNotIn(context_line, self.t.taskrc_content) def test_context_define_invalid_for_write_due_to_tag_exclusion(self): """Test definition of a context that is not a valid write context because it contains a tag exclusion.""" self.t.config("confirmation", "off") - code, out, err = self.t('context define nowork due:today -work') + code, out, err = self.t("context define nowork due:today -work") self.assertIn("Context 'nowork' defined", out) # Assert the config contains read context definition - context_line = 'context.nowork.read=due:today -work\n' + context_line = "context.nowork.read=due:today -work\n" self.assertIn(context_line, self.t.taskrc_content) # Assert that it contains the definition only once self.assertEqual(self.t.taskrc_content.count(context_line), 1) # Assert the config does not contain write context definition - context_line = 'context.nowork.write=due:today -work\n' + context_line = "context.nowork.write=due:today -work\n" self.assertNotIn(context_line, self.t.taskrc_content) # Assert that the write context was not set at all - self.assertNotIn('context.nowork.write=', self.t.taskrc_content) + self.assertNotIn("context.nowork.write=", self.t.taskrc_content) # Assert that legacy style was not used # Assert the config contains read context definition - context_line = 'context.nowork=due:today -work\n' + context_line = "context.nowork=due:today -work\n" self.assertNotIn(context_line, self.t.taskrc_content) def test_context_delete(self): """Test simple context deletion.""" - self.t('context define work project:Work', input='y\ny\n') - code, out, err = self.t('context delete work', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + code, out, err = self.t("context delete work", input="y\ny\n") self.assertIn("Context 'work' deleted.", out) # Assert that taskrc does not countain context work definition - self.assertFalse(any('context.work' in line for line in self.t.taskrc_content)) + self.assertFalse(any("context.work" in line for line in self.t.taskrc_content)) def test_context_delete_undefined(self): """Test deletion of undefined context.""" - code, out, err = self.t.runError('context delete foo', input='y\n') + code, out, err = self.t.runError("context delete foo", input="y\n") self.assertIn("Context 'foo' not found.", err) # Assert that taskrc does not countain context work definition - self.assertFalse(any('context.foo.read=' in line for line in self.t.taskrc_content)) - self.assertFalse(any('context.foo.write=' in line for line in self.t.taskrc_content)) + self.assertFalse( + any("context.foo.read=" in line for line in self.t.taskrc_content) + ) + self.assertFalse( + any("context.foo.write=" in line for line in self.t.taskrc_content) + ) def test_context_delete_unset_after_removal(self): """Test that context is unset if its definition has been removed.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context work') - code, out, err = self.t('context delete work', input='y\n\y\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context work") + code, out, err = self.t("context delete work", input="y\ny\n") self.assertIn("Context 'work' deleted.", out) # Assert that taskrc does not countain context work definition - self.assertFalse(any('context.work=' in line for line in self.t.taskrc_content)) - self.assertFalse(any('context.work.read=' in line for line in self.t.taskrc_content)) - self.assertFalse(any('context.work.write=' in line for line in self.t.taskrc_content)) + self.assertFalse(any("context.work=" in line for line in self.t.taskrc_content)) + self.assertFalse( + any("context.work.read=" in line for line in self.t.taskrc_content) + ) + self.assertFalse( + any("context.work.write=" in line for line in self.t.taskrc_content) + ) # Aseert that the context is not set - code, out, err = self.t('context show') - self.assertIn('No context is currently applied.', out) - self.assertFalse(any(re.search(r"^context(\.(read|write))?=", line) for line in self.t.taskrc_content)) + code, out, err = self.t("context show") + self.assertIn("No context is currently applied.", out) + self.assertFalse( + any( + re.search(r"^context(\.(read|write))?=", line) + for line in self.t.taskrc_content + ) + ) def test_context_list_active(self): """Test the 'context list' command.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') - self.t('context home') - code, out, err = self.t('context list') - contains_work = lambda line: 'work' in line and 'project:Work' in line and 'no' in line - contains_home = lambda line: 'home' in line and '+home' in line and 'yes' in line + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") + self.t("context home") + code, out, err = self.t("context list") + contains_work = ( + lambda line: "work" in line and "project:Work" in line and "no" in line + ) + contains_home = ( + lambda line: "home" in line and "+home" in line and "yes" in line + ) # Assert that output contains work and home context definitions exactly # once @@ -233,39 +251,41 @@ class ContextManagementTest(TestCase): def test_context_list_legacy(self): """Test the determination of legacy context definition.""" - self.t('config context.old project:Old', input='y\n') - self.t('context old') - code, out, err = self.t('context list') + self.t("config context.old project:Old", input="y\n") + self.t("context old") + code, out, err = self.t("context list") # Assert that "old" context has only the read component defined - self.assertRegex(out, r'read\s+project:Old\s+yes') - self.assertRegex(out, r'write\s+yes') + self.assertRegex(out, r"read\s+project:Old\s+yes") + self.assertRegex(out, r"write\s+yes") def test_context_initially_empty(self): """Test that no context is set initially.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") - code, out, err = self.t('context show') - self.assertIn('No context is currently applied.', out) - self.assertFalse(any(re.search("^context=", line) for line in self.t.taskrc_content)) + code, out, err = self.t("context show") + self.assertIn("No context is currently applied.", out) + self.assertFalse( + any(re.search("^context=", line) for line in self.t.taskrc_content) + ) def test_context_setting(self): """Test simple context setting.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home home", input="y\ny\n") - code, out, err = self.t('context home') + code, out, err = self.t("context home") self.assertIn("Context 'home' set.", out) self.assertIn("context=home\n", self.t.taskrc_content) def test_context_resetting(self): """Test resetting the same context.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") - self.t('context home') - code, out, err = self.t('context home') + self.t("context home") + code, out, err = self.t("context home") self.assertIn("Context 'home' set.", out) context_line = "context=home\n" @@ -273,92 +293,98 @@ class ContextManagementTest(TestCase): def test_context_switching(self): """Test changing the context.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") # Switch to home context - code, out, err = self.t('context home') + code, out, err = self.t("context home") self.assertIn("Context 'home' set.", out) self.assertEqual(self.t.taskrc_content.count("context=home\n"), 1) # Switch to work context - code, out, err = self.t('context work') + code, out, err = self.t("context work") self.assertIn("Context 'work' set.", out) self.assertNotIn("context=home\n", self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count("context=work\n"), 1) # Switch back to home context - code, out, err = self.t('context home') + code, out, err = self.t("context home") self.assertIn("Context 'home' set.", out) self.assertNotIn("context=work\n", self.t.taskrc_content) self.assertEqual(self.t.taskrc_content.count("context=home\n"), 1) def test_context_unsetting(self): """Test removing the context.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") - self.t('context home') - code, out, err = self.t('context none') + self.t("context home") + code, out, err = self.t("context none") # Assert expected output. self.assertIn("Context unset.", out) # Assert no context definition in the taskrc - contains_any_context = lambda line: re.match('^context=', line) - self.assertFalse(any(contains_any_context(line) for line in self.t.taskrc_content)) + contains_any_context = lambda line: re.match("^context=", line) + self.assertFalse( + any(contains_any_context(line) for line in self.t.taskrc_content) + ) # Assert no context showing up using show subcommand - code, out, err = self.t('context show') + code, out, err = self.t("context show") self.assertIn("No context is currently applied.", out) def test_context_unsetting_after_switching(self): """Test unsetting the context after changing the context around.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") # Switch to contexts around - self.t('context home') - self.t('context work') - self.t('context home') + self.t("context home") + self.t("context work") + self.t("context home") # Unset the context - code, out, err = self.t('context none') + code, out, err = self.t("context none") # Assert expected output. self.assertIn("Context unset.", out) # Assert no context definition in the taskrc - contains_any_context = lambda line: re.match('^context=', line) - self.assertFalse(any(contains_any_context(line) for line in self.t.taskrc_content)) + contains_any_context = lambda line: re.match("^context=", line) + self.assertFalse( + any(contains_any_context(line) for line in self.t.taskrc_content) + ) # Assert no context showing up using show subcommand - code, out, err = self.t('context show') + code, out, err = self.t("context show") self.assertIn("No context is currently applied.", out) def test_context_unsetting_with_no_context_set(self): """Test removing the context when no context is set.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") - code, out, err = self.t.runError('context none') + code, out, err = self.t.runError("context none") # Assert expected output. self.assertIn("Context not unset.", err) # Assert no context definition in the taskrc - contains_any_context = lambda line: re.match('^context=', line) - self.assertFalse(any(contains_any_context(line) for line in self.t.taskrc_content)) + contains_any_context = lambda line: re.match("^context=", line) + self.assertFalse( + any(contains_any_context(line) for line in self.t.taskrc_content) + ) # Assert no context showing up using show subcommand - code, out, err = self.t('context show') + code, out, err = self.t("context show") self.assertIn("No context is currently applied.", out) def test_context(self): """Test the _context command.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') - code, out, err = self.t('_context') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") + code, out, err = self.t("_context") # Assert expected output. self.assertIn("work", out.splitlines()) @@ -367,12 +393,12 @@ class ContextManagementTest(TestCase): def test_context_completion(self): """Test the _context command with some context set.""" - self.t('context define work project:Work', input='y\ny\n') - self.t('context define home +home', input='y\ny\n') + self.t("context define work project:Work", input="y\ny\n") + self.t("context define home +home", input="y\ny\n") # Activate some context - self.t('context work') - code, out, err = self.t('_context') + self.t("context work") + code, out, err = self.t("_context") # Assert expected output. self.assertIn("work", out.splitlines()) @@ -387,9 +413,9 @@ class ContextEvaluationTest(TestCase): self.t.config("confirmation", "off") # Setup contexts - self.t('context define work project:Work') - self.t('context define home +home') - self.t('context define today due:today') + self.t("context define work project:Work") + self.t("context define home +home") + self.t("context define today due:today") # Setup tasks self.t('add project:Work "work task"') @@ -399,7 +425,7 @@ class ContextEvaluationTest(TestCase): def test_context_evaluation(self): """Test the context applied with report list command.""" - code, out, err = self.t('list') + code, out, err = self.t("list") # Assert all the tasks are present in the output self.assertIn("work task", out) @@ -408,8 +434,8 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the home context and rerun the report - self.t('context home') - code, out, err = self.t('list') + self.t("context home") + code, out, err = self.t("list") # Assert all the tasks with the home tag are present in the output self.assertNotIn("work task", out) @@ -419,7 +445,7 @@ class ContextEvaluationTest(TestCase): def test_context_evaluation_switching(self): """Test swtiching context using the list report.""" - code, out, err = self.t('list') + code, out, err = self.t("list") # Assert all the tasks are present in the output self.assertIn("work task", out) @@ -428,8 +454,8 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the home context and rerun the report - self.t('context home') - code, out, err = self.t('list') + self.t("context home") + code, out, err = self.t("list") # Assert all the tasks with the home tag are present in the output self.assertNotIn("work task", out) @@ -438,8 +464,8 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the work context and rerun the report - self.t('context work') - code, out, err = self.t('list') + self.t("context work") + code, out, err = self.t("list") # Assert all the tasks with the home tag are present in the output self.assertIn("work task", out) @@ -448,8 +474,8 @@ class ContextEvaluationTest(TestCase): self.assertNotIn("home today task", out) # Set the today context and rerun the report - self.t('context today') - code, out, err = self.t('list') + self.t("context today") + code, out, err = self.t("list") # Assert all the tasks with the home tag are present in the output self.assertNotIn("work task", out) @@ -459,8 +485,8 @@ class ContextEvaluationTest(TestCase): def test_context_evaluation_unset(self): """Test unsetting context with report list command.""" - self.t('context home') - code, out, err = self.t('list') + self.t("context home") + code, out, err = self.t("list") # Assert all the tasks home tagged tasks are present self.assertNotIn("work task", out) @@ -469,8 +495,8 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the context to none - self.t('context none') - code, out, err = self.t('list') + self.t("context none") + code, out, err = self.t("list") # Assert all the tasks are present in the output self.assertIn("work task", out) @@ -482,8 +508,8 @@ class ContextEvaluationTest(TestCase): """Test the context applied with report list command combined with user filters.""" # Set the home context - self.t('context home') - code, out, err = self.t('list due:today') + self.t("context home") + code, out, err = self.t("list due:today") # Assert all the tasks are present in the output self.assertNotIn("work task", out) @@ -492,8 +518,8 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the work context and rerun the report - self.t('context work') - code, out, err = self.t('list due:today') + self.t("context work") + code, out, err = self.t("list due:today") # Assert all the tasks are present in the output self.assertNotIn("work task", out) @@ -507,10 +533,10 @@ class ContextEvaluationTest(TestCase): filters are used. """ - self.t('context home') + self.t("context home") # Try task not included in context - output = self.t('1 list')[1] + output = self.t("1 list")[1] # Assert that ID filter works even if it does not match the context self.assertIn("work task", output) @@ -519,7 +545,7 @@ class ContextEvaluationTest(TestCase): self.assertNotIn("home today task", output) # Try task included in context - output = self.t('2 list')[1] + output = self.t("2 list")[1] # Assert that ID filter works if it does match # the context (sanity check) @@ -529,7 +555,7 @@ class ContextEvaluationTest(TestCase): self.assertNotIn("home today task", output) # Test for combination of IDs - output = self.t('1 2 list')[1] + output = self.t("1 2 list")[1] # Assert that ID filter works if it partly matches # and partly does not match the context @@ -544,12 +570,12 @@ class ContextEvaluationTest(TestCase): filters are used. """ - self.t('context home') - first_uuid = self.t('_get 1.uuid')[1] - second_uuid = self.t('_get 2.uuid')[1] + self.t("context home") + first_uuid = self.t("_get 1.uuid")[1] + second_uuid = self.t("_get 2.uuid")[1] # Try task not included in context - output = self.t('%s list' % first_uuid)[1] + output = self.t("%s list" % first_uuid)[1] # Assert that UUID filter works even if it does not match # the context @@ -559,7 +585,7 @@ class ContextEvaluationTest(TestCase): self.assertNotIn("home today task", output) # Try task included in context - output = self.t('%s list' % second_uuid)[1] + output = self.t("%s list" % second_uuid)[1] # Assert that UUID filter works if it does match # the context (sanity check) @@ -569,7 +595,7 @@ class ContextEvaluationTest(TestCase): self.assertNotIn("home today task", output) # Test for combination of UUIDs - output = self.t('%s %s list' % (first_uuid, second_uuid))[1] + output = self.t("%s %s list" % (first_uuid, second_uuid))[1] # Assert that UUID filter works if it partly matches # and partly does not match the context @@ -586,7 +612,7 @@ class ContextEvaluationTest(TestCase): self.t.config("report.list.context", "0") # Get the tasks - code, out, err = self.t('list') + code, out, err = self.t("list") # Assert all the tasks are present in the output self.assertIn("work task", out) @@ -595,9 +621,9 @@ class ContextEvaluationTest(TestCase): self.assertIn("home today task", out) # Set the home context and rerun the report - self.t('context home') + self.t("context home") - code, out, err = self.t('list') + code, out, err = self.t("list") # Assert nothing changed - all the tasks are present in the output self.assertIn("work task", out) @@ -644,7 +670,10 @@ class ContextErrorHandling(TestCase): """Verify 'task context show' with contexts works""" self.t.config("confirmation", "off") code, out, err = self.t("context define work +work") - self.assertIn("Context 'work' defined (read, write). Use 'task context work' to activate.", out) + self.assertIn( + "Context 'work' defined (read, write). Use 'task context work' to activate.", + out, + ) code, out, err = self.t("context work") self.assertIn("Context 'work' set. Use 'task context none' to remove.", out) @@ -661,6 +690,7 @@ class ContextErrorHandling(TestCase): code, out, err = self.t("context show") self.assertIn("No context is currently applied.", out) + class TestBug1734(TestCase): def setUp(self): self.t = Task() @@ -680,6 +710,7 @@ class TestBug1734(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python syntax=python diff --git a/test/conversion b/test/conversion index 687c848ce..7b271134b 100755 --- a/test/conversion +++ b/test/conversion @@ -1,10 +1,10 @@ #!/bin/sh -printf "C++: %5d\n" $(ls *.t.cpp | wc -l) -printf "Python: %5d\n" $(head -n1 *.t | grep -a '\bpython' | wc -l) -printf "Bash: %5d\n" $(head -n1 *.t | grep -a '\bbash' | wc -l) +printf "C++: %5d\n" $(ls *.test.cpp | wc -l) +printf "Python: %5d\n" $(head -n1 *.test.py | grep -a '\bpython' | wc -l) +printf "Bash: %5d\n" $(head -n1 *.test.sh | grep -a '\bbash' | wc -l) echo -printf "Feature %5d\n" $(ls feature.*.t | wc -l) -printf "Bug %5d\n" $(ls tw-*.t | wc -l) +printf "Feature %5d\n" $(ls feature.*.test.py | wc -l) +printf "Bug %5d\n" $(ls tw-*.test.sh | wc -l) echo -printf "Total: %5d\n" $(ls *.t | wc -l) +printf "Total: %5d\n" $(ls *.test.* | wc -l) diff --git a/test/count.t b/test/count.test.py similarity index 98% rename from test/count.t rename to test/count.test.py index 0d87d01ca..d3f6969ed 100755 --- a/test/count.t +++ b/test/count.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -57,6 +57,7 @@ class TestCount(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/custom.config.t b/test/custom.config.test.py similarity index 91% rename from test/custom.config.t rename to test/custom.config.test.py index edf950faa..5124802b2 100755 --- a/test/custom.config.t +++ b/test/custom.config.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,10 +42,12 @@ class TestCustomConfig(TestCase): self.t.config("alias.xyzzyx", "status:waiting") self.t.config("imnotrecognized", "kai") - self.DIFFER_MSG = ("Some of your .taskrc variables differ from the " - "default values.") - self.NOT_RECOG_MSG = ("Your .taskrc file contains these unrecognized " - "variables:") + self.DIFFER_MSG = ( + "Some of your .taskrc variables differ from the " "default values." + ) + self.NOT_RECOG_MSG = ( + "Your .taskrc file contains these unrecognized " "variables:" + ) def test_show_alias(self): """task show - warns when non-default values are matched @@ -90,6 +92,7 @@ class TestCustomConfig(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/custom.recur_ind.t b/test/custom.recur_ind.test.py similarity index 87% rename from test/custom.recur_ind.t rename to test/custom.recur_ind.test.py index 9f4ab5959..a1a11e696 100755 --- a/test/custom.recur_ind.t +++ b/test/custom.recur_ind.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,26 +42,27 @@ class TestCustomRecurIndicator(TestCase): def test_recurrence_indicator(self): """Add a recurring and non-recurring task, look for the indicator.""" - self.t.config("report.foo.columns", "id,recur.indicator") - self.t.config("report.foo.labels", "ID,R") - self.t.config("report.foo.sort", "id+") - self.t.config("verbose", "nothing") + self.t.config("report.foo.columns", "id,recur.indicator") + self.t.config("report.foo.labels", "ID,R") + self.t.config("report.foo.sort", "id+") + self.t.config("verbose", "nothing") self.t("add foo due:tomorrow recur:weekly") self.t("add bar") code, out, err = self.t("foo") self.assertIn(" 1 R", out) - self.assertIn(" 2", out) + self.assertIn(" 2", out) self.assertIn(" 3 R", out) code, out, err = self.t("foo rc.recurrence.indicator=RE") self.assertIn(" 1 RE", out) - self.assertIn(" 2", out) + self.assertIn(" 2", out) self.assertIn(" 3 RE", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/custom.tag_ind.t b/test/custom.tag_ind.test.py similarity index 89% rename from test/custom.tag_ind.t rename to test/custom.tag_ind.test.py index 7bb8e8cdd..2c67897a9 100755 --- a/test/custom.tag_ind.t +++ b/test/custom.tag_ind.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,9 +41,9 @@ class TestCustomTagIndicator(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.foo.description", "DESC") - cls.t.config("report.foo.columns", "id,tags.indicator") - cls.t.config("report.foo.labels", "ID,T") - cls.t.config("report.foo.sort", "id+") + cls.t.config("report.foo.columns", "id,tags.indicator") + cls.t.config("report.foo.labels", "ID,T") + cls.t.config("report.foo.sort", "id+") cls.t("add foo +tag") cls.t("add bar") @@ -52,16 +52,17 @@ class TestCustomTagIndicator(TestCase): """Verify default tag indicator (+) is shown""" code, out, err = self.t("foo") self.assertRegex(out, "ID.+T") - self.assertRegex(out, "1\s+\+") + self.assertRegex(out, r"1\s+\+") def test_custom_indicator(self): """Verify custom tag indicator (TAG) is shown""" code, out, err = self.t("rc.tag.indicator:TAG foo") - self.assertRegex(out, "1\s+TAG") + self.assertRegex(out, r"1\s+TAG") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/custom.t b/test/custom.test.py similarity index 86% rename from test/custom.t rename to test/custom.test.py index ca6a36b6b..bf7a194ff 100755 --- a/test/custom.t +++ b/test/custom.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,15 +40,15 @@ class TestCustomReports(TestCase): """Executed before each test in the class""" self.t = Task() self.t.config("report.foo.description", "DESC") - self.t.config("report.foo.labels", "ID,DESCRIPTION") - self.t.config("report.foo.columns", "id,description") - self.t.config("report.foo.sort", "id+") - self.t.config("report.foo.filter", "project:A") + self.t.config("report.foo.labels", "ID,DESCRIPTION") + self.t.config("report.foo.columns", "id,description") + self.t.config("report.foo.sort", "id+") + self.t.config("report.foo.filter", "project:A") def test_custom_report_help(self): """Verify custom report description is shown in help""" code, out, err = self.t("help") - self.assertRegex(out, "task foo\s+DESC\n") + self.assertRegex(out, "task foo\\s+DESC\n") def test_custom_filter(self): """Verify custome report filtr is applied""" @@ -73,19 +73,23 @@ class TestCustomReports(TestCase): code, out, err = self.t("foo rc._forcecolor:on rc.report.foo.filter:") self.assertIn("[44m", out) + class TestCustomErrorHandling(TestCase): def setUp(self): self.t = Task() def test_size_mismatch(self): self.t.config("report.foo.columns", "id,description") - self.t.config("report.foo.labels", "id") + self.t.config("report.foo.labels", "id") code, out, err = self.t.runError("foo") - self.assertIn("There are different numbers of columns and labels for report 'foo'.", err) + self.assertIn( + "There are different numbers of columns and labels for report 'foo'.", err + ) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/date.iso.t b/test/date.iso.test.py similarity index 99% rename from test/date.iso.t rename to test/date.iso.test.py index 818df122f..41999314e 100755 --- a/test/date.iso.t +++ b/test/date.iso.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -61,6 +61,7 @@ class TestDateISOAndEpoch(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/dateformat.t b/test/dateformat.test.py similarity index 69% rename from test/dateformat.t rename to test/dateformat.test.py index c07df4c32..3a1ffd41e 100755 --- a/test/dateformat.t +++ b/test/dateformat.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,8 +41,8 @@ class TestDateformat(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("report.xxx.columns", "id,due") - cls.t.config("report.xxx.labels", "ID,DUE") - cls.t.config("report.xxx.sort", "id") + cls.t.config("report.xxx.labels", "ID,DUE") + cls.t.config("report.xxx.sort", "id") def setUp(self): """Executed before each test in the class""" @@ -56,6 +56,7 @@ class TestDateformat(TestCase): code, out, err = self.t("xxx rc.dateformat:YMDTHNS") self.assertEqual(out.count("20150704T000000"), 3) + class TestBug886(TestCase): def setUp(self): """Executed before each test in the class""" @@ -64,12 +65,12 @@ class TestBug886(TestCase): def test_invalid_day(self): """886: Test invalid day synonym - Bug 886: tw doesn't warn the user if, e.g., a weekday cannot be resolved properly + Bug 886: tw doesn't warn the user if, e.g., a weekday cannot be resolved properly """ - code, out, err =self.t("add one due:sun") + code, out, err = self.t("add one due:sun") self.assertIn("Created task 1.", out) - code, out, err =self.t.runError("add two due:donkey") + code, out, err = self.t.runError("add two due:donkey") self.assertIn("'donkey' is not a valid date", err) @@ -80,33 +81,36 @@ class TestBug986(TestCase): def test_dateformat_precedence(self): """986: Verify rc.dateformat.info takes precedence over rc.dateformat""" - self.t('add test') - self.t('1 start') + self.t("add test") + self.t("1 start") - code, out, err = self.t('1 info rc.dateformat:XX rc.dateformat.info:__') - self.assertIn('__', out) - self.assertNotIn('XX', out) + code, out, err = self.t("1 info rc.dateformat:XX rc.dateformat.info:__") + self.assertIn("__", out) + self.assertNotIn("XX", out) - code, out, err = self.t('1 info rc.dateformat:__ rc.dateformat.info:') - self.assertIn('__', out) + code, out, err = self.t("1 info rc.dateformat:__ rc.dateformat.info:") + self.assertIn("__", out) class TestBug1620(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() - self.t.config('dateformat', 'YMD-HN') + self.t.config("dateformat", "YMD-HN") def test_dateformat_overrides_iso(self): """1620: Verify that a defined dateformat overrides the ISO interpretation""" - code, out, err = self.t ('add pro:vorhaben due:20150601-1415 tatusmeeting vorbereiten') + code, out, err = self.t( + "add pro:vorhaben due:20150601-1415 tatusmeeting vorbereiten" + ) - code, out, err = self.t ('_get 1.due') + code, out, err = self.t("_get 1.due") self.assertEqual(out, "2015-06-01T14:15:00\n") - code, out, err = self.t ('long') + code, out, err = self.t("long") self.assertIn("20150601-1415", out) + class TestCapitalizedDays(TestCase): """Make sure capitalized names such as 'Friday' work. @@ -125,28 +129,30 @@ class TestCapitalizedDays(TestCase): def test_dateformat_capitalized(self): """Verify upper case days and months work""" # Lower case: - code, out, err = self.t('add sometask due:mon') - code, out, err = self.t('add sometask due:monday') - code, out, err = self.t('add sometask due:jan') - code, out, err = self.t('add sometask due:january') + code, out, err = self.t("add sometask due:mon") + code, out, err = self.t("add sometask due:monday") + code, out, err = self.t("add sometask due:jan") + code, out, err = self.t("add sometask due:january") # Upper case days of the week - code, out, err = self.t('add sometask due:Tue') - code, out, err = self.t('add sometask due:Tuesday') - code, out, err = self.t('add sometask due:Thu') - code, out, err = self.t('add sometask due:Thursday') + code, out, err = self.t("add sometask due:Tue") + code, out, err = self.t("add sometask due:Tuesday") + code, out, err = self.t("add sometask due:Thu") + code, out, err = self.t("add sometask due:Thursday") # Upper case months: - code, out, err = self.t('add sometask due:Jan') - code, out, err = self.t('add sometask due:January') - code, out, err = self.t('add sometask due:Jun') - code, out, err = self.t('add sometask due:June') - code, out, err = self.t('add sometask due:May') + code, out, err = self.t("add sometask due:Jan") + code, out, err = self.t("add sometask due:January") + code, out, err = self.t("add sometask due:Jun") + code, out, err = self.t("add sometask due:June") + code, out, err = self.t("add sometask due:May") # Incorrect: - code, out, err = self.t.runError('add sometask due:Yo') - code, out, err = self.t.runError('add sometask due:TU') + code, out, err = self.t.runError("add sometask due:Yo") + code, out, err = self.t.runError("add sometask due:TU") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/datesort.t b/test/datesort.test.py similarity index 83% rename from test/datesort.t rename to test/datesort.test.py index 85cafb2b8..81712ad89 100755 --- a/test/datesort.t +++ b/test/datesort.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,13 +42,13 @@ class TestDateSort(TestCase): def test_datesort(self): """Verify dates sort properly with a report date format that hides date details""" - self.t.config("verbose", "nothing") - self.t.config("dateformat", "YMD") - self.t.config("report.small_list.columns", "due,description") - self.t.config("report.small_list.labels", "Due,Description") - self.t.config("report.small_list.sort", "due+") - self.t.config("report.small_list.filter", "status:pending") - self.t.config("report.small_list.dateformat", "D") + self.t.config("verbose", "nothing") + self.t.config("dateformat", "YMD") + self.t.config("report.small_list.columns", "due,description") + self.t.config("report.small_list.labels", "Due,Description") + self.t.config("report.small_list.sort", "due+") + self.t.config("report.small_list.filter", "status:pending") + self.t.config("report.small_list.dateformat", "D") self.t("add one due:20150101") self.t("add two due:20150201") @@ -66,6 +66,7 @@ class TestDateSort(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/datetime-negative.t b/test/datetime-negative.test.py similarity index 66% rename from test/datetime-negative.t rename to test/datetime-negative.test.py index 31d3e4b76..5d80639c9 100755 --- a/test/datetime-negative.t +++ b/test/datetime-negative.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -45,10 +44,10 @@ class BaseDateTimeNegativeTest(TestCase): self.t = Task() def assertInvalidDatetimeFormat(self, value): - self.t.runError('add due:{0} test1'.format(value)) - self.t.runError('add scheduled:{0} test2'.format(value)) - self.t.runError('add wait:{0} test3'.format(value)) - self.t.runError('add until:{0} test4'.format(value)) + self.t.runError("add due:{0} test1".format(value)) + self.t.runError("add scheduled:{0} test2".format(value)) + self.t.runError("add wait:{0} test3".format(value)) + self.t.runError("add until:{0} test4".format(value)) class TestIncorrectDate(BaseDateTimeNegativeTest): @@ -59,60 +58,60 @@ class TestIncorrectDate(BaseDateTimeNegativeTest): """ def test_set_incorrect_datetime_randomstring(self): - self.assertInvalidDatetimeFormat('random') + self.assertInvalidDatetimeFormat("random") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_in_YYYY_MM_DD(self): - self.assertInvalidDatetimeFormat('-2014-07-07') + self.assertInvalidDatetimeFormat("-2014-07-07") def test_set_incorrect_datetime_missing_day_in_YYYY_MM_DD(self): - self.assertInvalidDatetimeFormat('2014-07-') + self.assertInvalidDatetimeFormat("2014-07-") def test_set_incorrect_datetime_month_zero_in_YYYY_MM_DD(self): - self.assertInvalidDatetimeFormat('2014-0-12') + self.assertInvalidDatetimeFormat("2014-0-12") def test_set_incorrect_datetime_invalid_characters_in_YYYY_MM_DD(self): - self.assertInvalidDatetimeFormat('abcd-ab-ab') + self.assertInvalidDatetimeFormat("abcd-ab-ab") def test_set_incorrect_datetime_day_as_zeros_in_YYYY_DDD(self): - self.assertInvalidDatetimeFormat('2014-000') + self.assertInvalidDatetimeFormat("2014-000") def test_set_incorrect_datetime_overlap_day_in_nonoverlap_year_in_YYYY_DDD(self): - self.assertInvalidDatetimeFormat('2014-366') + self.assertInvalidDatetimeFormat("2014-366") def test_set_incorrect_datetime_medium_overlap_day_in_YYYY_DDD(self): - self.assertInvalidDatetimeFormat('2014-999') + self.assertInvalidDatetimeFormat("2014-999") def test_set_incorrect_datetime_huge_overlap_day_in_YYYY_DDD(self): - self.assertInvalidDatetimeFormat('2014-999999999') + self.assertInvalidDatetimeFormat("2014-999999999") def test_set_incorrect_datetime_week_with_the_number_zero_in_YYYY_Www(self): - self.assertInvalidDatetimeFormat('2014-W00') + self.assertInvalidDatetimeFormat("2014-W00") def test_set_incorrect_datetime_overflow_in_week_in_YYYY_Www(self): - self.assertInvalidDatetimeFormat('2014-W54') + self.assertInvalidDatetimeFormat("2014-W54") # Unsupported non-extended form. def test_set_incorrect_datetime_day_zero_in_YYYY_WwwD(self): - self.assertInvalidDatetimeFormat('2014-W240') + self.assertInvalidDatetimeFormat("2014-W240") # Unsupported non-extended form. def test_set_incorrect_datetime_day_eight_in_YYYY_WwwD(self): - self.assertInvalidDatetimeFormat('2014-W248') + self.assertInvalidDatetimeFormat("2014-W248") # Unsupported non-extended form. def test_set_incorrect_datetime_day_two_hundred_in_YYYY_WwwD(self): - self.assertInvalidDatetimeFormat('2014-W24200') + self.assertInvalidDatetimeFormat("2014-W24200") # Disabled: Looks like 'hhmm-hh' - #def test_set_incorrect_datetime_month_zero_in_YYYY_MM(self): + # def test_set_incorrect_datetime_month_zero_in_YYYY_MM(self): # self.assertInvalidDatetimeFormat('2014-00') def test_set_incorrect_datetime_overflow_month_in_YYYY_MM(self): - self.assertInvalidDatetimeFormat('2014-13') + self.assertInvalidDatetimeFormat("2014-13") def test_set_incorrect_datetime_huge_overflow_month_in_YYYY_MM(self): - self.assertInvalidDatetimeFormat('2014-99') + self.assertInvalidDatetimeFormat("2014-99") class TestIncorrectTime(BaseDateTimeNegativeTest): @@ -122,276 +121,276 @@ class TestIncorrectTime(BaseDateTimeNegativeTest): """ def test_set_incorrect_datetime_hour_overflow_in_hh_mm(self): - self.assertInvalidDatetimeFormat('25:00') + self.assertInvalidDatetimeFormat("25:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm(self): - self.assertInvalidDatetimeFormat('99:00') + self.assertInvalidDatetimeFormat("99:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm(self): - self.assertInvalidDatetimeFormat('12:60') + self.assertInvalidDatetimeFormat("12:60") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm(self): - self.assertInvalidDatetimeFormat('12:99') + self.assertInvalidDatetimeFormat("12:99") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm(self): - self.assertInvalidDatetimeFormat('12:ab') + self.assertInvalidDatetimeFormat("12:ab") def test_set_incorrect_datetime_invalid_hours_in_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:12') + self.assertInvalidDatetimeFormat("ab:12") def test_set_incorrect_datetime_invalid_time_in_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:cd') + self.assertInvalidDatetimeFormat("ab:cd") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm(self): - self.assertInvalidDatetimeFormat('-12:12') + self.assertInvalidDatetimeFormat("-12:12") def test_set_incorrect_datetime_negative_minutes_in_hh_mm(self): - self.assertInvalidDatetimeFormat('12:-12') + self.assertInvalidDatetimeFormat("12:-12") def test_set_incorrect_datetime_hour_overflow_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('25:00Z') + self.assertInvalidDatetimeFormat("25:00Z") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('99:00Z') + self.assertInvalidDatetimeFormat("99:00Z") def test_set_incorrect_datetime_minute_overflow_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('12:60Z') + self.assertInvalidDatetimeFormat("12:60Z") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('12:99Z') + self.assertInvalidDatetimeFormat("12:99Z") def test_set_incorrect_datetime_invalid_minutes_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('12:abZ') + self.assertInvalidDatetimeFormat("12:abZ") def test_set_incorrect_datetime_invalid_hours_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('ab:12Z') + self.assertInvalidDatetimeFormat("ab:12Z") def test_set_incorrect_datetime_invalid_time_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('ab:cdZ') + self.assertInvalidDatetimeFormat("ab:cdZ") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('-12:12Z') + self.assertInvalidDatetimeFormat("-12:12Z") def test_set_incorrect_datetime_negative_minutes_in_hh_mmZ(self): - self.assertInvalidDatetimeFormat('12:-12Z') + self.assertInvalidDatetimeFormat("12:-12Z") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('25:00+01:00') + self.assertInvalidDatetimeFormat("25:00+01:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('99:00+01:00') + self.assertInvalidDatetimeFormat("99:00+01:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:60+01:00') + self.assertInvalidDatetimeFormat("12:60+01:00") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:99+01:00') + self.assertInvalidDatetimeFormat("12:99+01:00") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:ab+01:00') + self.assertInvalidDatetimeFormat("12:ab+01:00") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:12+01:00') + self.assertInvalidDatetimeFormat("ab:12+01:00") def test_set_incorrect_datetime_invalid_time_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:cd+01:00') + self.assertInvalidDatetimeFormat("ab:cd+01:00") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('-12:12+01:00') + self.assertInvalidDatetimeFormat("-12:12+01:00") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:-12+01:00') + self.assertInvalidDatetimeFormat("12:-12+01:00") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('25:00-01:00') + self.assertInvalidDatetimeFormat("25:00-01:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('99:00-01:00') + self.assertInvalidDatetimeFormat("99:00-01:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:60-01:00') + self.assertInvalidDatetimeFormat("12:60-01:00") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:99-01:00') + self.assertInvalidDatetimeFormat("12:99-01:00") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:ab-01:00') + self.assertInvalidDatetimeFormat("12:ab-01:00") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:12-01:00') + self.assertInvalidDatetimeFormat("ab:12-01:00") def test_set_incorrect_datetime_invalid_time_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:cd-01:00') + self.assertInvalidDatetimeFormat("ab:cd-01:00") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('-12:12-01:00') + self.assertInvalidDatetimeFormat("-12:12-01:00") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:-12-01:00') + self.assertInvalidDatetimeFormat("12:-12-01:00") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('25:00:00') + self.assertInvalidDatetimeFormat("25:00:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('99:00:00') + self.assertInvalidDatetimeFormat("99:00:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:60:00') + self.assertInvalidDatetimeFormat("12:60:00") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:99:00') + self.assertInvalidDatetimeFormat("12:99:00") def test_set_incorrect_datetime_second_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:12:60') + self.assertInvalidDatetimeFormat("12:12:60") def test_set_incorrect_datetime_huge_second_overflow_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:12:99') + self.assertInvalidDatetimeFormat("12:12:99") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:ab:00') + self.assertInvalidDatetimeFormat("12:ab:00") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('ab:12:00') + self.assertInvalidDatetimeFormat("ab:12:00") def test_set_incorrect_datetime_invalid_seconds_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:12:ab') + self.assertInvalidDatetimeFormat("12:12:ab") def test_set_incorrect_datetime_invalid_time_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('ab:cd:ef') + self.assertInvalidDatetimeFormat("ab:cd:ef") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('-12:12:12') + self.assertInvalidDatetimeFormat("-12:12:12") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:-12:12') + self.assertInvalidDatetimeFormat("12:-12:12") def test_set_incorrect_datetime_negative_seconds_in_hh_mm_ss(self): - self.assertInvalidDatetimeFormat('12:12:-12') + self.assertInvalidDatetimeFormat("12:12:-12") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('25:00:00Z') + self.assertInvalidDatetimeFormat("25:00:00Z") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('99:00:00Z') + self.assertInvalidDatetimeFormat("99:00:00Z") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:60:00Z') + self.assertInvalidDatetimeFormat("12:60:00Z") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:99:00Z') + self.assertInvalidDatetimeFormat("12:99:00Z") def test_set_incorrect_datetime_second_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:12:60Z') + self.assertInvalidDatetimeFormat("12:12:60Z") def test_set_incorrect_datetime_huge_second_overflow_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:12:99Z') + self.assertInvalidDatetimeFormat("12:12:99Z") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:ab:00Z') + self.assertInvalidDatetimeFormat("12:ab:00Z") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('ab:12:00Z') + self.assertInvalidDatetimeFormat("ab:12:00Z") def test_set_incorrect_datetime_invalid_seconds_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:12:abZ') + self.assertInvalidDatetimeFormat("12:12:abZ") def test_set_incorrect_datetime_invalid_time_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('ab:cd:efZ') + self.assertInvalidDatetimeFormat("ab:cd:efZ") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('-12:12:12Z') + self.assertInvalidDatetimeFormat("-12:12:12Z") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:-12:12Z') + self.assertInvalidDatetimeFormat("12:-12:12Z") def test_set_incorrect_datetime_negative_seconds_in_hh_mm_ssZ(self): - self.assertInvalidDatetimeFormat('12:12:-12Z') + self.assertInvalidDatetimeFormat("12:12:-12Z") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('25:00:00+01:00') + self.assertInvalidDatetimeFormat("25:00:00+01:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('95:00:00+01:00') + self.assertInvalidDatetimeFormat("95:00:00+01:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:60:00+01:00') + self.assertInvalidDatetimeFormat("12:60:00+01:00") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:99:00+01:00') + self.assertInvalidDatetimeFormat("12:99:00+01:00") def test_set_incorrect_datetime_second_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:60+01:00') + self.assertInvalidDatetimeFormat("12:12:60+01:00") def test_set_incorrect_datetime_huge_second_overflow_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:99+01:00') + self.assertInvalidDatetimeFormat("12:12:99+01:00") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:ab:00+01:00') + self.assertInvalidDatetimeFormat("12:ab:00+01:00") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:12:00+01:00') + self.assertInvalidDatetimeFormat("ab:12:00+01:00") def test_set_incorrect_datetime_invalid_seconds_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:ab+01:00') + self.assertInvalidDatetimeFormat("12:12:ab+01:00") def test_set_incorrect_datetime_invalid_time_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:cd:ef+01:00') + self.assertInvalidDatetimeFormat("ab:cd:ef+01:00") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('-12:12:12+01:00') + self.assertInvalidDatetimeFormat("-12:12:12+01:00") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:-12:12+01:00') + self.assertInvalidDatetimeFormat("12:-12:12+01:00") def test_set_incorrect_datetime_negative_seconds_in_hh_mm_ss_plus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:-12+01:00') + self.assertInvalidDatetimeFormat("12:12:-12+01:00") def test_set_incorrect_datetime_hour_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('25:00:00-01:00') + self.assertInvalidDatetimeFormat("25:00:00-01:00") def test_set_incorrect_datetime_huge_hour_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('95:00:00-01:00') + self.assertInvalidDatetimeFormat("95:00:00-01:00") def test_set_incorrect_datetime_minute_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:60:00-01:00') + self.assertInvalidDatetimeFormat("12:60:00-01:00") def test_set_incorrect_datetime_huge_minute_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:99:00-01:00') + self.assertInvalidDatetimeFormat("12:99:00-01:00") def test_set_incorrect_datetime_second_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:60-01:00') + self.assertInvalidDatetimeFormat("12:12:60-01:00") def test_set_incorrect_datetime_huge_second_overflow_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:99-01:00') + self.assertInvalidDatetimeFormat("12:12:99-01:00") def test_set_incorrect_datetime_invalid_minutes_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:ab:00-01:00') + self.assertInvalidDatetimeFormat("12:ab:00-01:00") def test_set_incorrect_datetime_invalid_hours_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:12:00-01:00') + self.assertInvalidDatetimeFormat("ab:12:00-01:00") def test_set_incorrect_datetime_invalid_seconds_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:ab-01:00') + self.assertInvalidDatetimeFormat("12:12:ab-01:00") def test_set_incorrect_datetime_invalid_time_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('ab:cd:ef-01:00') + self.assertInvalidDatetimeFormat("ab:cd:ef-01:00") @unittest.skip # see #2996 def test_set_incorrect_datetime_negative_hours_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('-12:12:12-01:00') + self.assertInvalidDatetimeFormat("-12:12:12-01:00") def test_set_incorrect_datetime_negative_minutes_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:-12:12-01:00') + self.assertInvalidDatetimeFormat("12:-12:12-01:00") def test_set_incorrect_datetime_negative_seconds_in_hh_mm_ss_minus_hh_mm(self): - self.assertInvalidDatetimeFormat('12:12:-12-01:00') + self.assertInvalidDatetimeFormat("12:12:-12-01:00") # There were a group of tests that failed for the wrong reason, and these # have been removed. @@ -435,8 +434,10 @@ class TestIncorrectTime(BaseDateTimeNegativeTest): # 12:12+03:2 # 12:12+3:2 + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/debug.t b/test/debug.test.py similarity index 99% rename from test/debug.t rename to test/debug.test.py index 2469213be..1838a51d9 100755 --- a/test/debug.t +++ b/test/debug.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -88,6 +88,7 @@ class TestDebugMode(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/default.t b/test/default.test.py similarity index 92% rename from test/default.t rename to test/default.test.py index 8d7339a3b..8afdf91f5 100755 --- a/test/default.t +++ b/test/default.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,8 +41,8 @@ class TestCMD(TestCase): cls.t = Task() cls.t.config("default.command", "list") - cls.t('add one') - cls.t('add two') + cls.t("add one") + cls.t("add two") def test_default_command(self): """default command""" @@ -52,8 +52,8 @@ class TestCMD(TestCase): def test_info_command(self): """info command""" - code, out, err = self.t('1') - self.assertRegex(out, 'Description\s+one') + code, out, err = self.t("1") + self.assertRegex(out, r"Description\s+one") class TestDefaults(TestCase): @@ -61,11 +61,11 @@ class TestDefaults(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("default.command", "list") - cls.t.config("default.project", "PROJECT") + cls.t.config("default.command", "list") + cls.t.config("default.project", "PROJECT") cls.t.config("uda.priority.default", "M") - cls.t.config("default.due", "eom") - cls.t.config("default.scheduled", "eom") + cls.t.config("default.due", "eom") + cls.t.config("default.scheduled", "eom") def test_all_defaults(self): """Verify all defaults are employed""" @@ -126,6 +126,7 @@ class TestBug1377(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/delete.t b/test/delete.test.py similarity index 97% rename from test/delete.t rename to test/delete.test.py index 02d544b22..29953559c 100755 --- a/test/delete.t +++ b/test/delete.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -47,7 +47,7 @@ class TestDelete(TestCase): uuid = out.strip() self.t("1 delete", input="y\n") - self.t.runError("list") # GC/handleRecurrence + self.t.runError("list") # GC/handleRecurrence code, out, err = self.t("_get 1.status") self.assertEqual("\n", out) @@ -78,7 +78,7 @@ class TestDelete(TestCase): code, out, err = self.t("1 done") self.assertIn("Completed 1 task.", out) - self.t("all") # GC/handleRecurrence + self.t("all") # GC/handleRecurrence code, out, err = self.t("%s delete" % uuid, input="y\n") self.assertIn("Deleted 1 task.", out) @@ -142,6 +142,7 @@ class TestDelete(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/denotate.t b/test/denotate.test.py similarity index 96% rename from test/denotate.t rename to test/denotate.test.py index d0e65f815..50b81f773 100755 --- a/test/denotate.t +++ b/test/denotate.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -69,7 +69,9 @@ class TestDenotate(TestCase): # Failed partial match, one annotation code, out, err = self.t.runError("rc.search.case.sensitive=yes 2 denotate AL") - self.assertIn("Did not find any matching annotation to be deleted for 'AL'.", out) + self.assertIn( + "Did not find any matching annotation to be deleted for 'AL'.", out + ) # Exact match, two annotations code, out, err = self.t("1 denotate beta") @@ -92,6 +94,7 @@ class TestDenotate(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/dependencies.t b/test/dependencies.test.py similarity index 91% rename from test/dependencies.t rename to test/dependencies.test.py index 171fd1d4b..e9a6bc307 100755 --- a/test/dependencies.t +++ b/test/dependencies.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import string import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -82,9 +82,9 @@ class TestDependencies(TestCase): def test_circular_5(self): """Check circular dependencies are caught, using 5 tasks""" - self.t("add three") - self.t("add four") - self.t("add five") + self.t("add three") + self.t("add four") + self.t("add five") self.t("5 modify dep:4") self.t("4 modify dep:3") self.t("3 modify dep:2") @@ -117,9 +117,9 @@ class TestDependencies(TestCase): def test_modify_multiple(self): """Check circular dependencies are caught, using 5 tasks""" - self.t("add three") - self.t("add four") - self.t("add five") + self.t("add three") + self.t("add four") + self.t("add five") code, out, err = self.t("1 modify dep:2,3,4") self.assertIn("Modified 1 task.", out) @@ -236,18 +236,18 @@ class TestBug697(TestCase): def test_blocking_to_recurring(self): """697: Verify that making a blocking task into a recurring task breaks dependencies - Bug 697: Making a blocking task recur breaks dependency. - 1. Create 2 tasks: "foo" and "bar". - 2. Give "bar" a due date. - 3. Make "foo" depend on "bar". - 4. Make "bar" recur yearly. + Bug 697: Making a blocking task recur breaks dependency. + 1. Create 2 tasks: "foo" and "bar". + 2. Give "bar" a due date. + 3. Make "foo" depend on "bar". + 4. Make "bar" recur yearly. """ self.t("add one") self.t("add two") self.t("2 modify due:eom") self.t("1 modify depends:2") self.t("2 modify recur:yearly") - self.t("list") # GC/handleRecurrence + self.t("list") # GC/handleRecurrence # The problem is that although 1 --> 2, 2 is now a recurring parent, and as 1 # depends on the parent UUID, it is not something transferred to the child on @@ -333,21 +333,21 @@ class TestFeature725(TestCase): class Test1481(TestCase): def setUp(self): self.t = Task() - self.t('add parent') - self.t('add child') - self.t('add child2') - self.child1_uuid = self.t.export_one(2)['uuid'] - self.child2_uuid = self.t.export_one(3)['uuid'] + self.t("add parent") + self.t("add child") + self.t("add child2") + self.child1_uuid = self.t.export_one(2)["uuid"] + self.child2_uuid = self.t.export_one(3)["uuid"] def test_set_dependency_on_first_completed_task(self): """1481: Sets dependency on task which has been just completed.""" - self.t('2 done') + self.t("2 done") # Trigger the GC to clear up IDs - self.t('next') + self.t("next") # Set the dependency - self.t('1 modify depends:%s' % self.child1_uuid) + self.t("1 modify depends:%s" % self.child1_uuid) def test_set_dependency_on_second_completed_task(self): """ @@ -355,26 +355,25 @@ class Test1481(TestCase): before most recently completed task. """ - self.t('2 done') - self.t('3 done') + self.t("2 done") + self.t("3 done") # Trigger the GC to clear up IDs - self.t('next') + self.t("next") # Set the dependencies - self.t('1 modify depends:%s' % self.child2_uuid) + self.t("1 modify depends:%s" % self.child2_uuid) def test_set_dependency_on_two_completed_tasks(self): - """ 1481: Sets dependency on two most recent completed tasks. """ - self.t('2 done') - self.t('3 done') + """1481: Sets dependency on two most recent completed tasks.""" + self.t("2 done") + self.t("3 done") # Trigger the GC to clear up IDs - self.t('next') + self.t("next") # Set the dependencies - self.t('1 modify depends:%s,%s' % (self.child1_uuid, - self.child2_uuid)) + self.t("1 modify depends:%s,%s" % (self.child1_uuid, self.child2_uuid)) # TODO - test dependency.confirmation config variable @@ -386,6 +385,7 @@ class Test1481(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/diag.t b/test/diag.test.py similarity index 87% rename from test/diag.t rename to test/diag.test.py index f19b3e8ce..1c9a824a8 100755 --- a/test/diag.t +++ b/test/diag.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import platform import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,20 +39,19 @@ from basetest import Task, TestCase class TestDiagnostics(TestCase): def setUp(self): self.t = Task() - self.t.config("editor", "edlin") + self.t.config("editor", "edlin") @unittest.skipIf( - getattr(platform, 'dist', None) == None or 'xenial' == platform.dist()[-1], - 'Skipping diagnostics test on Ubuntu 16.04, as it lacks full C++17 support' + getattr(platform, "dist", None) == None or "xenial" == platform.dist()[-1], + "Skipping diagnostics test on Ubuntu 16.04, as it lacks full C++17 support", ) def test_diagnostics(self): """Task diag output, so we can monitor platforms""" self.t.activate_hooks() code, out, err = self.t.diag() self.tap(out) - self.assertRegex(out, "Compliance:\s+C\+\+17") + self.assertRegex(out, r"Compliance:\s+C\+\+17") self.assertIn("edlin", out) - self.assertIn("Locking", out) def test_64bit_time_t(self): """Test that time_t has size of 64 bits""" @@ -62,6 +61,7 @@ class TestDiagnostics(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/diag_color.t b/test/diag_color.test.py similarity index 99% rename from test/diag_color.t rename to test/diag_color.test.py index aef9bf780..c82305e78 100755 --- a/test/diag_color.t +++ b/test/diag_color.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -70,6 +70,7 @@ class TestDiagColor(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/docker/arch b/test/docker/arch index 643462630..1f01e38b1 100644 --- a/test/docker/arch +++ b/test/docker/arch @@ -26,7 +26,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test/ -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/debianstable b/test/docker/debianstable index 878553350..fc31168c6 100644 --- a/test/docker/debianstable +++ b/test/docker/debianstable @@ -27,7 +27,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/debiantesting b/test/docker/debiantesting index 78773eef8..dbf886149 100644 --- a/test/docker/debiantesting +++ b/test/docker/debiantesting @@ -27,7 +27,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/fedora38 b/test/docker/fedora40 similarity index 83% rename from test/docker/fedora38 rename to test/docker/fedora40 index e254f1eb2..8dfc23dd3 100644 --- a/test/docker/fedora38 +++ b/test/docker/fedora40 @@ -1,4 +1,4 @@ -FROM fedora:38 +FROM fedora:40 RUN dnf update -y RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y @@ -26,7 +26,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/fedora39 b/test/docker/fedora41 similarity index 83% rename from test/docker/fedora39 rename to test/docker/fedora41 index c4b3bd670..c1093bfe6 100644 --- a/test/docker/fedora39 +++ b/test/docker/fedora41 @@ -1,4 +1,4 @@ -FROM fedora:39 +FROM fedora:41 RUN dnf update -y RUN dnf install python3 git gcc gcc-c++ cmake make libuuid-devel libfaketime glibc-langpack-en curl -y @@ -26,7 +26,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/opensuse b/test/docker/opensuse index 94a2d5a3a..cff676ef1 100644 --- a/test/docker/opensuse +++ b/test/docker/opensuse @@ -25,7 +25,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/ubuntu2004 b/test/docker/ubuntu2004 index efef83221..dd0ec6f57 100644 --- a/test/docker/ubuntu2004 +++ b/test/docker/ubuntu2004 @@ -34,7 +34,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/docker/ubuntu2204 b/test/docker/ubuntu2204 index 29381323a..c1c3f6aef 100644 --- a/test/docker/ubuntu2204 +++ b/test/docker/ubuntu2204 @@ -27,7 +27,5 @@ RUN cmake --install build RUN task --version # Setup tests -WORKDIR /root/code/build/test -RUN make -j8 - -CMD ["bash", "-c", "./run_all -v ; cat all.log | grep 'not ok' ; ./problems"] +RUN cmake --build build --target test_runner -j 8 +CMD ctest --test-dir build -j 8 --output-on-failure --rerun-failed diff --git a/test/dom2.t b/test/dom2.test.py similarity index 76% rename from test/dom2.t rename to test/dom2.test.py index 8907ac97f..0cd2403c0 100755 --- a/test/dom2.t +++ b/test/dom2.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -53,199 +53,201 @@ class TestDOM(TestCase): cls.t("3 annotate note") # Add task containing UDA attributes - cls.t("add ticket task " - "ticketdate:2015-09-04 " - "ticketest:hour " - "ticketnote:comment " - "ticketnum:47") + cls.t( + "add ticket task " + "ticketdate:2015-09-04 " + "ticketest:hour " + "ticketnote:comment " + "ticketnum:47" + ) def test_dom_no_ref(self): - """ DOM missing reference """ + """DOM missing reference""" code, out, err = self.t.runError("_get") self.assertEqual("No DOM reference specified.\n", err) def test_dom_bad_ref(self): - """ DOM bad reference """ + """DOM bad reference""" code, out, err = self.t.runError("_get donkey") self.assertEqual("'donkey' is not a DOM reference.\n", err) def test_dom_task_ref(self): - """ DOM reference to other task """ + """DOM reference to other task""" code, out, err = self.t("_get 2.due") self.assertIn("2011-09-01T00:00:00", out) def test_dom_cli_ref(self): - """ DOM reference to current command line """ + """DOM reference to current command line""" code, out, err = self.t("_get 3.wait") self.assertIn("2011-09-01T00:00:00", out) def test_dom_id_uuid_roundtrip(self): - """ DOM id/uuid roundtrip """ + """DOM id/uuid roundtrip""" code, out, err = self.t("_get 1.uuid") uuid = out.strip() code, out, err = self.t("_get {0}.id".format(uuid)) self.assertEqual("1\n", out) def test_dom_fail(self): - """ DOM lookup of missing item """ + """DOM lookup of missing item""" code, out, err = self.t("_get 5.description") self.assertEqual("\n", out) def test_dom_tags(self): - """ DOM 3.tags """ + """DOM 3.tags""" code, out, err = self.t("_get 3.tags") self.assertEqual("tag1,tag2\n", out) def test_dom_tags_tag1(self): - """ DOM 3.tags.tag1 """ + """DOM 3.tags.tag1""" code, out, err = self.t("_get 3.tags.tag1") self.assertEqual("tag1\n", out) def test_dom_tags_OVERDUE(self): - """ DOM 3.tags.OVERDUE """ + """DOM 3.tags.OVERDUE""" code, out, err = self.t("_get 3.tags.OVERDUE") self.assertEqual("OVERDUE\n", out) def test_dom_due_year(self): - """ DOM 3.due.year """ + """DOM 3.due.year""" code, out, err = self.t("_get 3.due.year") self.assertEqual("2011\n", out) def test_dom_due_month(self): - """ DOM 3.due.month """ + """DOM 3.due.month""" code, out, err = self.t("_get 3.due.month") self.assertEqual("9\n", out) def test_dom_due_day(self): - """ DOM 3.due.day """ + """DOM 3.due.day""" code, out, err = self.t("_get 3.due.day") self.assertEqual("1\n", out) def test_dom_due_week(self): - """ DOM 3.due.week """ + """DOM 3.due.week""" code, out, err = self.t("_get 3.due.week") self.assertEqual("35\n", out) def test_dom_due_weekday(self): - """ DOM 3.due.weekday """ + """DOM 3.due.weekday""" code, out, err = self.t("_get 3.due.weekday") self.assertEqual("4\n", out) def test_dom_due_hour(self): - """ DOM 3.due.hour """ + """DOM 3.due.hour""" code, out, err = self.t("_get 3.due.hour") self.assertEqual("0\n", out) def test_dom_due_minute(self): - """ DOM 3.due.minute """ + """DOM 3.due.minute""" code, out, err = self.t("_get 3.due.minute") self.assertEqual("0\n", out) def test_dom_due_second(self): - """ DOM 3.due.second """ + """DOM 3.due.second""" code, out, err = self.t("_get 3.due.second") self.assertEqual("0\n", out) def test_dom_annotation_count_1(self): - """ DOM 1.annotation.count """ + """DOM 1.annotation.count""" code, out, err = self.t("_get 1.annotations.count") self.assertEqual("0\n", out) def test_dom_annotation_count_3(self): - """ DOM 3.annotation.count """ + """DOM 3.annotation.count""" code, out, err = self.t("_get 3.annotations.count") self.assertEqual("1\n", out) def test_dom_annotation_entry(self): - """ DOM 3.annotations.1.entry """ + """DOM 3.annotations.1.entry""" code, out, err = self.t("_get 3.annotations.1.entry") self.assertRegex(out, r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}") def test_dom_annotation_entry_second(self): - """ DOM 3.annotations.1.entry """ + """DOM 3.annotations.1.entry""" code, out, err = self.t("_get 3.annotations.1.entry.second") self.assertRegex(out, r"\d{1,2}") def test_dom_annotation_description(self): - """ DOM 3.annotations.1.description """ + """DOM 3.annotations.1.description""" code, out, err = self.t("_get 3.annotations.1.description") self.assertIn("note\n", out) def test_dom_system_version(self): - """ DOM system.version """ + """DOM system.version""" code, out, err = self.t("_get system.version") self.assertEqual(code, 0) self.assertRegex(out, r"\d\.\d+\.\d+") def test_dom_system_os(self): - """ DOM system.os """ + """DOM system.os""" code, out, err = self.t("_get system.os") self.assertEqual(code, 0) self.assertEqual(len(out) > 4, True) self.assertNotIn("", out) def test_dom_tw_program(self): - """ DOM tw.program """ + """DOM tw.program""" code, out, err = self.t("_get tw.program") self.assertEqual(code, 0) self.assertIn("task", out) def test_dom_tw_args(self): - """ DOM tw.args """ + """DOM tw.args""" code, out, err = self.t("_get tw.args") self.assertEqual(code, 0) self.assertIn("task _get tw.args", out) def test_dom_tw_width(self): - """ DOM tw.width """ + """DOM tw.width""" code, out, err = self.t("_get tw.width") self.assertEqual(code, 0) self.assertRegex(out, r"\d+") def test_dom_tw_height(self): - """ DOM tw.height """ + """DOM tw.height""" code, out, err = self.t("_get tw.height") self.assertEqual(code, 0) self.assertRegex(out, r"\d+") def test_dom_tw_version(self): - """ DOM tw.version """ + """DOM tw.version""" code, out, err = self.t("_get tw.version") self.assertEqual(code, 0) self.assertRegex(out, r"\d\.\d+\.\d+") def test_dom_context_program(self): - """ DOM context.program """ + """DOM context.program""" code, out, err = self.t("_get context.program") self.assertEqual(code, 0) self.assertIn("task", out) def test_dom_context_args(self): - """ DOM context.args """ + """DOM context.args""" code, out, err = self.t("_get context.args") self.assertEqual(code, 0) self.assertIn("task _get context.args", out) def test_dom_context_width(self): - """ DOM context.width """ + """DOM context.width""" code, out, err = self.t("_get context.width") self.assertEqual(code, 0) self.assertRegex(out, r"\d+") def test_dom_context_height(self): - """ DOM context.height """ + """DOM context.height""" code, out, err = self.t("_get context.height") self.assertEqual(code, 0) self.assertRegex(out, r"\d+") def test_dom_rc_name(self): - """ DOM rc.dateformat """ + """DOM rc.dateformat""" code, out, err = self.t("_get rc.dateformat") self.assertEqual(code, 0) self.assertIn("YMD", out) def test_dom_rc_missing(self): - """ DOM rc.missing """ + """DOM rc.missing""" code, out, err = self.t("_get rc.missing") self.assertEqual("\n", out) @@ -275,10 +277,11 @@ class TestDOM(TestCase): self.assertIn("2015-09-04T00:00:00", out) def test_dom_uda_date_year(self): - """ DOM 3.due.year """ + """DOM 3.due.year""" code, out, err = self.t("_get 4.ticketdate.year") self.assertEqual("2015\n", out) + class TestDOMSync(TestCase): """ This class verifies that the 'tw.syncneeded' DOM reference properly @@ -289,14 +292,14 @@ class TestDOMSync(TestCase): self.t = Task() def test_dom_tw_syncneeded_false(self): - """ DOM tw.syncneeded --> false """ + """DOM tw.syncneeded --> false""" code, out, err = self.t("_get tw.syncneeded") self.assertEqual(code, 0) self.assertIn("0", out) self.assertNotIn("1k", out) def test_dom_tw_syncneeded_true(self): - """ DOM tw.syncneeded --> true """ + """DOM tw.syncneeded --> true""" self.t("add foo") code, out, err = self.t("_get tw.syncneeded") self.assertEqual(code, 0) @@ -340,83 +343,83 @@ class TestDOMDirectReferencesOnAddition(TestCase): cls.t("1 annotate Second annotation") def test_dom_reference_due(self): - """ DOM reference on due in add command """ + """DOM reference on due in add command""" self.t("add test_due due:1.due") latest = self.t.latest - self.assertEqual("test_due", latest['description']) - self.assertEqual("20150901T080000Z", latest['due']) + self.assertEqual("test_due", latest["description"]) + self.assertEqual("20150901T080000Z", latest["due"]) def test_dom_reference_project(self): - """ DOM reference on project in add command """ + """DOM reference on project in add command""" self.t("add test_project project:1.project") latest = self.t.latest - self.assertEqual("test_project", latest['description']) - self.assertEqual("baseproject", latest['project']) + self.assertEqual("test_project", latest["description"]) + self.assertEqual("baseproject", latest["project"]) def test_dom_reference_tags_all(self): - """ DOM reference on tags in add command """ + """DOM reference on tags in add command""" self.t("add test_tags_all tags:1.tags") latest = self.t.latest - self.assertEqual("test_tags_all", latest['description']) - self.assertEqual(["tag1","tag2"], latest['tags']) + self.assertEqual("test_tags_all", latest["description"]) + self.assertEqual(["tag1", "tag2"], latest["tags"]) def test_dom_reference_tags_single(self): - """ DOM reference on specific tag in add command """ + """DOM reference on specific tag in add command""" self.t("add test_tags_single tags:1.tags.tag1") latest = self.t.latest - self.assertEqual("test_tags_single", latest['description']) - self.assertEqual(["tag1"], latest['tags']) + self.assertEqual("test_tags_single", latest["description"]) + self.assertEqual(["tag1"], latest["tags"]) def test_dom_reference_annotation(self): - """ DOM reference on annotation description in add command """ + """DOM reference on annotation description in add command""" self.t("add description:1.annotations.2.description") latest = self.t.latest - self.assertEqual("Second annotation", latest['description']) + self.assertEqual("Second annotation", latest["description"]) def test_dom_reference_numeric_uda(self): - """ DOM reference on numeric UDA in add command """ + """DOM reference on numeric UDA in add command""" self.t("add test_numeric_uda ticketnum:1.ticketnum") latest = self.t.latest - self.assertEqual("test_numeric_uda", latest['description']) - self.assertEqual(42, latest['ticketnum']) + self.assertEqual("test_numeric_uda", latest["description"]) + self.assertEqual(42, latest["ticketnum"]) def test_dom_reference_date_uda(self): - """ DOM reference on date UDA in add command """ + """DOM reference on date UDA in add command""" self.t("add test_date_uda ticketdate:1.ticketdate") latest = self.t.latest - self.assertEqual("test_date_uda", latest['description']) - self.assertEqual("20150903T080000Z", latest['ticketdate']) + self.assertEqual("test_date_uda", latest["description"]) + self.assertEqual("20150903T080000Z", latest["ticketdate"]) def test_dom_reference_string_uda(self): - """ DOM reference on string UDA in add command """ + """DOM reference on string UDA in add command""" self.t("add test_string_uda ticketnote:1.ticketnote") latest = self.t.latest - self.assertEqual("test_string_uda", latest['description']) - self.assertEqual("This is awesome", latest['ticketnote']) + self.assertEqual("test_string_uda", latest["description"]) + self.assertEqual("This is awesome", latest["ticketnote"]) def test_dom_reference_string_value_uda(self): - """ DOM reference on string with limited values UDA in add command """ + """DOM reference on string with limited values UDA in add command""" self.t("add test_string_value_uda ticketflag:1.ticketflag") latest = self.t.latest - self.assertEqual("test_string_value_uda", latest['description']) - self.assertEqual("B", latest['ticketflag']) + self.assertEqual("test_string_value_uda", latest["description"]) + self.assertEqual("B", latest["ticketflag"]) def test_dom_reference_duration_uda(self): - """ DOM reference on duration UDA in add command """ + """DOM reference on duration UDA in add command""" self.t("add test_duration_uda ticketest:1.ticketest") latest = self.t.latest - self.assertEqual("test_duration_uda", latest['description']) - self.assertEqual("PT1H", latest['ticketest']) + self.assertEqual("test_duration_uda", latest["description"]) + self.assertEqual("PT1H", latest["ticketest"]) class TestDOMDirectReferencesFiltering(TestCase): @@ -456,44 +459,44 @@ class TestDOMDirectReferencesFiltering(TestCase): cls.t("add non matching task") def test_dom_filter_reference_due(self): - """ DOM reference on due in filter """ + """DOM reference on due in filter""" result = self.t.export_one("due:1.due") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_project(self): - """ DOM reference on project in filter """ + """DOM reference on project in filter""" result = self.t.export_one("project:1.project") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_tags_all(self): - """ DOM reference on tags in filter """ + """DOM reference on tags in filter""" result = self.t.export_one("tags:1.tags") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_numeric_uda(self): - """ DOM reference on numeric UDA in filter """ + """DOM reference on numeric UDA in filter""" result = self.t.export_one("ticketnum:1.ticketnum") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_date_uda(self): - """ DOM reference on date UDA in filter """ + """DOM reference on date UDA in filter""" result = self.t.export_one("ticketdate:1.ticketdate") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_string_uda(self): - """ DOM reference on string UDA in filter """ + """DOM reference on string UDA in filter""" result = self.t.export_one("ticketnote:1.ticketnote") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_string_value_uda(self): - """ DOM reference on string with limited values UDA in filter """ + """DOM reference on string with limited values UDA in filter""" result = self.t.export_one("ticketflag:1.ticketflag") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) def test_dom_filter_reference_duration_uda(self): - """ DOM reference on duration UDA in filter """ + """DOM reference on duration UDA in filter""" result = self.t.export_one("ticketest:1.ticketest") - self.assertEqual("matching task", result['description']) + self.assertEqual("matching task", result["description"]) class TestBug1300(TestCase): @@ -502,18 +505,17 @@ class TestBug1300(TestCase): cls.t = Task() def test_dom_exit_status_good(self): - """1300: If the DOM recognizes a reference, it should return '0' - """ + """1300: If the DOM recognizes a reference, it should return '0'""" self.t("_get context.program") def test_dom_exit_status_bad(self): - """1300: If the DOM does not recognize a reference, it should return '1' - """ + """1300: If the DOM does not recognize a reference, it should return '1'""" self.t.runError("_get XYZ") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/dom.t.cpp b/test/dom_test.cpp similarity index 54% rename from test/dom.t.cpp rename to test/dom_test.cpp index 214955cda..f542b3e30 100644 --- a/test/dom.t.cpp +++ b/test/dom_test.cpp @@ -25,72 +25,69 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include +// cmake.h include header must come first + #include #include +#include + +namespace { //////////////////////////////////////////////////////////////////////////////// -bool providerString (const std::string& path, Variant& var) -{ - if (path == "name") - { - var = Variant ("value"); +bool providerString(const std::string& path, Variant& var) { + if (path == "name") { + var = Variant("value"); return true; - } - else if (path == "name.next") - { - var = Variant ("value.next"); + } else if (path == "name.next") { + var = Variant("value.next"); return true; - } - else if (path == "foo") - { - var = Variant ("bar"); + } else if (path == "foo") { + var = Variant("bar"); return true; - } - else if (path == "name.size") - { - var = Variant (6); + } else if (path == "name.size") { + var = Variant(6); return true; } return false; } +} // namespace + //////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (12); +int TEST_NAME(int, char**) { + UnitTest t(12); DOM dom; - t.is (dom.count (), 0, "DOM empty count is zero"); + t.is(dom.count(), 0, "DOM empty count is zero"); - dom.addSource ("name", &providerString); - dom.addSource ("name.next", &providerString); - dom.addSource ("name.size", &providerString); - dom.addSource ("foo", &providerString); - t.diag (dom.dump ()); - t.is (dom.count (), 4, "DOM now contains 4 nodes"); + dom.addSource("name", &providerString); + dom.addSource("name.next", &providerString); + dom.addSource("name.size", &providerString); + dom.addSource("foo", &providerString); + t.diag(dom.dump()); + t.is(dom.count(), 4, "DOM now contains 4 nodes"); - t.ok (dom.valid ("name"), "DOM 'name' valid"); - t.ok (dom.valid ("name.next"), "DOM 'name.next' valid"); - t.ok (dom.valid ("name.size"), "DOM 'name.size' valid"); - t.ok (dom.valid ("foo"), "DOM 'foo' valid"); - t.notok (dom.valid ("missing"), "DOM 'missing' not valid"); + t.ok(dom.valid("name"), "DOM 'name' valid"); + t.ok(dom.valid("name.next"), "DOM 'name.next' valid"); + t.ok(dom.valid("name.size"), "DOM 'name.size' valid"); + t.ok(dom.valid("foo"), "DOM 'foo' valid"); + t.notok(dom.valid("missing"), "DOM 'missing' not valid"); - auto v = dom.get ("name"); - t.is (v.get_string (), "value", "DOM get 'name' --> 'value'"); + auto v = dom.get("name"); + t.is(v.get_string(), "value", "DOM get 'name' --> 'value'"); - v = dom.get ("name.next"); - t.is (v.get_string (), "value.next", "DOM get 'name.next' --> 'value.next'"); + v = dom.get("name.next"); + t.is(v.get_string(), "value.next", "DOM get 'name.next' --> 'value.next'"); - v = dom.get ("name.size"); - t.is (v.get_integer (), 6, "DOM get 'name.size' --> 6"); + v = dom.get("name.size"); + t.is(v.get_integer(), 6, "DOM get 'name.size' --> 6"); - v = dom.get ("foo"); - t.is (v.get_string (), "bar", "DOM get 'name.size' --> 6"); + v = dom.get("foo"); + t.is(v.get_string(), "bar", "DOM get 'name.size' --> 6"); - v = dom.get ("missing"); - t.is (v.get_string (), "", "DOM get 'missing' --> ''"); + v = dom.get("missing"); + t.is(v.get_string(), "", "DOM get 'missing' --> ''"); return 0; } diff --git a/test/due.t b/test/due.test.py similarity index 96% rename from test/due.t rename to test/due.test.py index 82792473f..c78f4688b 100755 --- a/test/due.t +++ b/test/due.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest from datetime import datetime, timedelta + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -64,8 +64,8 @@ class TestDue(TestCase): def test_due(self): """due tasks displayed correctly""" code, out, err = self.t("list") - self.assertRegex(out, "\033\[31m.+{0}.+\033\[0m".format(self.just)) - self.assertRegex(out, "\s+{0}\s+".format(self.almost)) + self.assertRegex(out, "\033\\[31m.+{0}.+\033\\[0m".format(self.just)) + self.assertRegex(out, r"\s+{0}\s+".format(self.almost)) class TestBug418(TestCase): @@ -139,6 +139,7 @@ class TestBug2519(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/duplicate.t b/test/duplicate.test.py similarity index 93% rename from test/duplicate.t rename to test/duplicate.test.py index 573c2355b..d1bef3049 100755 --- a/test/duplicate.t +++ b/test/duplicate.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -65,7 +65,10 @@ class TestDuplication(TestCase): def test_duplication_showing_uuid(self): """Verify duplicate can show uuid""" code, out, err = self.t("1 duplicate rc.verbose:new-uuid") - self.assertRegex(out, "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}") + self.assertRegex( + out, + "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", + ) class TestDuplication2(TestCase): @@ -76,7 +79,7 @@ class TestDuplication2(TestCase): def test_duplication_recurrence(self): """Verify that recurring tasks are properly duplicated""" self.t("add R due:tomorrow recur:weekly") - self.t("list") # To force handleRecurrence(). + self.t("list") # To force handleRecurrence(). code, out, err = self.t("1 duplicate") self.assertIn("The duplicated task is too", out) @@ -84,7 +87,7 @@ class TestDuplication2(TestCase): code, out, err = self.t("2 duplicate") self.assertIn("The duplicated task is not", out) - self.t("list") # To force handleRecurrence(). + self.t("list") # To force handleRecurrence(). code, out, err = self.t("1 export") self.assertIn('"status":"recurring"', out) @@ -106,6 +109,7 @@ class TestDuplication2(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/edit.t b/test/edit.test.py similarity index 94% rename from test/edit.t rename to test/edit.test.py index 9a56f60a9..57891f732 100755 --- a/test/edit.t +++ b/test/edit.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -87,7 +87,9 @@ class TestTaskEdit(TestCase): self.t.config("uda.uorphan.type", "string") self.t.config("uda.uorphan.label", "uorphan") - self.t("add foo project:P +tag priority:H start:now due:eom wait:eom scheduled:eom recur:P1M until:eoy udate:now uduration:1day ustring:Hi unumeric:42 uorphan:Annie") + self.t( + "add foo project:P +tag priority:H start:now due:eom wait:eom scheduled:eom recur:P1M until:eoy udate:now uduration:1day ustring:Hi unumeric:42 uorphan:Annie" + ) self.t("1 annotate bar", input="n\n") # Make the orphan. @@ -100,6 +102,7 @@ class TestTaskEdit(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/encoding.t b/test/encoding.test.py similarity index 95% rename from test/encoding.t rename to test/encoding.test.py index 9fc3409a4..13b4973c6 100755 --- a/test/encoding.t +++ b/test/encoding.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import re import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -70,14 +70,15 @@ class TestUtf8(TestCase): code, out, err = self.t("ls") - expected = re.compile("\S\s{4}abc", re.MULTILINE) + expected = re.compile(r"\S\s{4}abc", re.MULTILINE) self.assertRegex(out, expected) - expected = re.compile("\S\s{5}def", re.MULTILINE) + expected = re.compile(r"\S\s{5}def", re.MULTILINE) self.assertRegex(out, expected) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/enpassant.t b/test/enpassant.test.py similarity index 83% rename from test/enpassant.t rename to test/enpassant.test.py index ce0d9ccde..b68c0639a 100755 --- a/test/enpassant.t +++ b/test/enpassant.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -55,19 +55,23 @@ class TestEnpassantMultiple(BaseTestEnpassant): code, out, err = self.t((id, "info")) self.assertRegex( - out, "Status +Completed", + out, + "Status +Completed", msg="enpassant {0} status change".format(id), ) self.assertRegex( - out, "Priority +H", + out, + "Priority +H", msg="enpassant {0} priority change".format(id), ) self.assertRegex( - out, "Tags +tag", + out, + "Tags +tag", msg="enpassant {0} tag change".format(id), ) self.assertRegex( - out, "Description +{0}".format(desc), + out, + "Description +{0}".format(desc), msg="enpassant {0} description change".format(id), ) @@ -95,28 +99,33 @@ class TestEnpassant(BaseTestEnpassant): def perform_action(self, action): self.t(("1", action, "oneanno")) code, out, err = self.t("1 info") - self.assertRegex(out, "Description +one\n[0-9: -]+ oneanno", - msg="{0} enpassant annotation".format(action)) + self.assertRegex( + out, + "Description +one\n[0-9: -]+ oneanno", + msg="{0} enpassant annotation".format(action), + ) self.t(("2", action, "/two/TWO/")) code, out, err = self.t("2 info") - self.assertRegex(out, "Description +TWO", - msg="{0} enpassant modify".format(action)) + self.assertRegex( + out, "Description +TWO", msg="{0} enpassant modify".format(action) + ) self.t(("3", action, "+threetag")) code, out, err = self.t("3 info") - self.assertRegex(out, "Tags +threetag", - msg="{0} enpassant tag".format(action)) + self.assertRegex(out, "Tags +threetag", msg="{0} enpassant tag".format(action)) self.t(("4", action, "pri:H")) code, out, err = self.t("4 info") - self.assertRegex(out, "Priority +H", - msg="{0} enpassant priority".format(action)) + self.assertRegex( + out, "Priority +H", msg="{0} enpassant priority".format(action) + ) self.t(("5", action, "pro:PROJ")) code, out, err = self.t("5 info") - self.assertRegex(out, "Project +PROJ", - msg="{0} enpassant project".format(action)) + self.assertRegex( + out, "Project +PROJ", msg="{0} enpassant project".format(action) + ) def test_done(self): """Test 'done' with en-passant changes""" @@ -139,6 +148,7 @@ class TestEnpassant(BaseTestEnpassant): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/eval.t.cpp b/test/eval.t.cpp deleted file mode 100644 index 987df0617..000000000 --- a/test/eval.t.cpp +++ /dev/null @@ -1,166 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -// A few hard-coded symbols. -bool get (const std::string& name, Variant& value) -{ - if (name == "x") - value = Variant (true); - else - return false; - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (52); - - // Test the source independently. - Variant v; - t.notok (get ("-", v), "true <-- get(-)"); - - t.ok (get ("x", v), "true <-- get(x)"); - t.is (v.type (), Variant::type_boolean, "get(x) --> boolean"); - t.is (v.get_bool (), true, "get(x) --> true"); - - Eval e; - e.addSource (get); - Variant result; - e.evaluatePostfixExpression ("x", result); - t.is (result.type (), Variant::type_boolean, "postfix 'x' --> boolean"); - t.is (result.get_bool (), true, "postfix 'x' --> true"); - - e.evaluatePostfixExpression ("pi", result); - t.is (result.type (), Variant::type_real, "postfix 'pi' --> real"); - t.is (result.get_real (), 3.141592, 0.00001, "postfix 'pi' --> 3.14159265"); - - e.evaluatePostfixExpression ("foo", result); - t.is (result.type (), Variant::type_string, "postfix 'foo' --> string"); - t.is (result.get_string (), "foo", "postfix 'foo' --> 'foo'"); - - // Simple infix arithmetic. - e.evaluateInfixExpression ("1+2", result); - t.is (result.type (), Variant::type_integer, "infix '1 + 2' --> integer"); - t.is (result.get_integer (), 3, "infix '1 + 2' --> 3"); - - // Simple postfix arithmetic. - e.evaluatePostfixExpression ("1 2 +", result); - t.is (result.type (), Variant::type_integer, "postfix '1 2 +' --> integer"); - t.is (result.get_integer (), 3, "postfix '1 2 +' --> 3"); - - e.evaluatePostfixExpression ("1 2 -", result); - t.is (result.type (), Variant::type_integer, "postfix '1 2 -' --> integer"); - t.is (result.get_integer (), -1, "postfix '1 2 -' --> -1"); - - e.evaluatePostfixExpression ("2 3 *", result); - t.is (result.type (), Variant::type_integer, "postfix '2 3 *' --> integer"); - t.is (result.get_integer (), 6, "postfix '2 3 *' --> 6"); - - e.evaluatePostfixExpression ("5 2 /", result); - t.is (result.type (), Variant::type_integer, "postfix '5 2 /' --> integer"); - t.is (result.get_integer (), 2, "postfix '5 2 /' --> 2"); - - e.evaluatePostfixExpression ("5 2 /", result); - t.is (result.type (), Variant::type_integer, "postfix '5 2 *' --> integer"); - t.is (result.get_integer (), 2, "postfix '5 2 *' --> 2"); - - // Simple postfix unary operator. - e.evaluatePostfixExpression ("0 !", result); - t.is (result.type (), Variant::type_boolean, "postfix '0 !' --> boolean"); - t.is (result.get_bool (), true, "postfix '0 !' --> true"); - - e.evaluatePostfixExpression ("1 !", result); - t.is (result.type (), Variant::type_boolean, "postfix '1 !' --> boolean"); - t.is (result.get_bool (), false, "postfix '1 !' --> false"); - - // Type promotion simple postfix arithmetic. - e.evaluatePostfixExpression ("1 2.3 +", result); - t.is (result.type (), Variant::type_real, "postfix '1 2.3 +' --> real"); - t.is (result.get_real (), 3.3, "postfix '1 2.3 +' --> 3.3"); - - e.evaluatePostfixExpression ("5 2.0 /", result); - t.is (result.type (), Variant::type_real, "postfix '5 2.0 /' --> integer"); - t.is (result.get_real (), 2.5, "postfix '5 2.0 /' --> 2.5"); - - // Simple logic. - e.evaluatePostfixExpression ("0 0 ||", result); - t.is (result.type (), Variant::type_boolean, "postfix '0 0 ||' --> boolean"); - t.is (result.get_bool (), false, "postfix '0 0 ||' --> false"); - - e.evaluatePostfixExpression ("0 1 ||", result); - t.is (result.type (), Variant::type_boolean, "postfix '0 1 ||' --> boolean"); - t.is (result.get_bool (), true, "postfix '0 1 ||' --> true"); - - e.evaluatePostfixExpression ("1 0 ||", result); - t.is (result.type (), Variant::type_boolean, "postfix '1 0 ||' --> boolean"); - t.is (result.get_bool (), true, "postfix '1 0 ||' --> true"); - - e.evaluatePostfixExpression ("1 1 ||", result); - t.is (result.type (), Variant::type_boolean, "postfix '1 1 ||' --> boolean"); - t.is (result.get_bool (), true, "postfix '1 1 ||' --> true"); - - e.evaluateInfixExpression ("2*3+1", result); - t.is (result.type (), Variant::type_integer, "infix '2*3+1' --> integer"); - t.is (result.get_integer (), 7, "infix '2*3+1' --> 7"); - - // TW-1254 - Unary minus support. - e.evaluateInfixExpression ("2- -3", result); - t.is (result.type (), Variant::type_integer, "infix '2- -3' --> integer"); - t.is (result.get_integer (), 5, "infix '2- -3' --> 5"); - - //e.debug (); - e.evaluateInfixExpression ("!false", result); - t.is (result.type (), Variant::type_boolean, "infix '!false' --> boolean"); - t.is (result.get_bool (), true, "infix '!false' --> true"); - - e.evaluateInfixExpression ("!true", result); - t.is (result.type (), Variant::type_boolean, "infix '!true' --> boolean"); - t.is (result.get_bool (), false, "infix '!true' --> false"); - - // _neg_ - e.evaluateInfixExpression ("- 1", result); - t.is (result.type (), Variant::type_integer, "infix '- 1' --> integer"); - t.is (result.get_integer (), -1, "infix '- 1' --> -1"); - - e.evaluateInfixExpression ("- 1.2", result); - t.is (result.type (), Variant::type_real, "infix '- 1.2' --> real"); - t.is (result.get_real (), -1.2, "infix '- 1.2' --> -1.2"); - - e.evaluateInfixExpression ("- 2days", result); - t.is (result.type (), Variant::type_duration, "infix '- 2days' --> duration"); - t.is (result.get_duration (), -86400*2, "infix '- 2days' --> -86400 * 2"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/eval_test.cpp b/test/eval_test.cpp new file mode 100644 index 000000000..e55d612a2 --- /dev/null +++ b/test/eval_test.cpp @@ -0,0 +1,173 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include + +namespace { + +//////////////////////////////////////////////////////////////////////////////// +// A few hard-coded symbols. +bool get(const std::string& name, Variant& value) { + if (name == "x") + value = Variant(true); + else + return false; + + return true; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(52); + Context context; + Context::setContext(&context); + + // Test the source independently. + Variant v; + t.notok(get("-", v), "true <-- get(-)"); + + t.ok(get("x", v), "true <-- get(x)"); + t.is(v.type(), Variant::type_boolean, "get(x) --> boolean"); + t.is(v.get_bool(), true, "get(x) --> true"); + + Eval e; + e.addSource(get); + Variant result; + e.evaluatePostfixExpression("x", result); + t.is(result.type(), Variant::type_boolean, "postfix 'x' --> boolean"); + t.is(result.get_bool(), true, "postfix 'x' --> true"); + + e.evaluatePostfixExpression("pi", result); + t.is(result.type(), Variant::type_real, "postfix 'pi' --> real"); + t.is(result.get_real(), 3.141592, 0.00001, "postfix 'pi' --> 3.14159265"); + + e.evaluatePostfixExpression("foo", result); + t.is(result.type(), Variant::type_string, "postfix 'foo' --> string"); + t.is(result.get_string(), "foo", "postfix 'foo' --> 'foo'"); + + // Simple infix arithmetic. + e.evaluateInfixExpression("1+2", result); + t.is(result.type(), Variant::type_integer, "infix '1 + 2' --> integer"); + t.is(result.get_integer(), 3, "infix '1 + 2' --> 3"); + + // Simple postfix arithmetic. + e.evaluatePostfixExpression("1 2 +", result); + t.is(result.type(), Variant::type_integer, "postfix '1 2 +' --> integer"); + t.is(result.get_integer(), 3, "postfix '1 2 +' --> 3"); + + e.evaluatePostfixExpression("1 2 -", result); + t.is(result.type(), Variant::type_integer, "postfix '1 2 -' --> integer"); + t.is(result.get_integer(), -1, "postfix '1 2 -' --> -1"); + + e.evaluatePostfixExpression("2 3 *", result); + t.is(result.type(), Variant::type_integer, "postfix '2 3 *' --> integer"); + t.is(result.get_integer(), 6, "postfix '2 3 *' --> 6"); + + e.evaluatePostfixExpression("5 2 /", result); + t.is(result.type(), Variant::type_integer, "postfix '5 2 /' --> integer"); + t.is(result.get_integer(), 2, "postfix '5 2 /' --> 2"); + + e.evaluatePostfixExpression("5 2 /", result); + t.is(result.type(), Variant::type_integer, "postfix '5 2 *' --> integer"); + t.is(result.get_integer(), 2, "postfix '5 2 *' --> 2"); + + // Simple postfix unary operator. + e.evaluatePostfixExpression("0 !", result); + t.is(result.type(), Variant::type_boolean, "postfix '0 !' --> boolean"); + t.is(result.get_bool(), true, "postfix '0 !' --> true"); + + e.evaluatePostfixExpression("1 !", result); + t.is(result.type(), Variant::type_boolean, "postfix '1 !' --> boolean"); + t.is(result.get_bool(), false, "postfix '1 !' --> false"); + + // Type promotion simple postfix arithmetic. + e.evaluatePostfixExpression("1 2.3 +", result); + t.is(result.type(), Variant::type_real, "postfix '1 2.3 +' --> real"); + t.is(result.get_real(), 3.3, "postfix '1 2.3 +' --> 3.3"); + + e.evaluatePostfixExpression("5 2.0 /", result); + t.is(result.type(), Variant::type_real, "postfix '5 2.0 /' --> integer"); + t.is(result.get_real(), 2.5, "postfix '5 2.0 /' --> 2.5"); + + // Simple logic. + e.evaluatePostfixExpression("0 0 ||", result); + t.is(result.type(), Variant::type_boolean, "postfix '0 0 ||' --> boolean"); + t.is(result.get_bool(), false, "postfix '0 0 ||' --> false"); + + e.evaluatePostfixExpression("0 1 ||", result); + t.is(result.type(), Variant::type_boolean, "postfix '0 1 ||' --> boolean"); + t.is(result.get_bool(), true, "postfix '0 1 ||' --> true"); + + e.evaluatePostfixExpression("1 0 ||", result); + t.is(result.type(), Variant::type_boolean, "postfix '1 0 ||' --> boolean"); + t.is(result.get_bool(), true, "postfix '1 0 ||' --> true"); + + e.evaluatePostfixExpression("1 1 ||", result); + t.is(result.type(), Variant::type_boolean, "postfix '1 1 ||' --> boolean"); + t.is(result.get_bool(), true, "postfix '1 1 ||' --> true"); + + e.evaluateInfixExpression("2*3+1", result); + t.is(result.type(), Variant::type_integer, "infix '2*3+1' --> integer"); + t.is(result.get_integer(), 7, "infix '2*3+1' --> 7"); + + // TW-1254 - Unary minus support. + e.evaluateInfixExpression("2- -3", result); + t.is(result.type(), Variant::type_integer, "infix '2- -3' --> integer"); + t.is(result.get_integer(), 5, "infix '2- -3' --> 5"); + + // e.debug (); + e.evaluateInfixExpression("!false", result); + t.is(result.type(), Variant::type_boolean, "infix '!false' --> boolean"); + t.is(result.get_bool(), true, "infix '!false' --> true"); + + e.evaluateInfixExpression("!true", result); + t.is(result.type(), Variant::type_boolean, "infix '!true' --> boolean"); + t.is(result.get_bool(), false, "infix '!true' --> false"); + + // _neg_ + e.evaluateInfixExpression("- 1", result); + t.is(result.type(), Variant::type_integer, "infix '- 1' --> integer"); + t.is(result.get_integer(), -1, "infix '- 1' --> -1"); + + e.evaluateInfixExpression("- 1.2", result); + t.is(result.type(), Variant::type_real, "infix '- 1.2' --> real"); + t.is(result.get_real(), -1.2, "infix '- 1.2' --> -1.2"); + + e.evaluateInfixExpression("- 2days", result); + t.is(result.type(), Variant::type_duration, "infix '- 2days' --> duration"); + t.is(result.get_duration(), -86400 * 2, "infix '- 2days' --> -86400 * 2"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/exec.t b/test/exec.test.py similarity index 99% rename from test/exec.t rename to test/exec.test.py index e9c8742b8..3cad4e32c 100755 --- a/test/exec.t +++ b/test/exec.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -64,8 +64,10 @@ class TestBug1414(TestCase): code, out, err = self.t() self.assertIn("hello", out) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/export.t b/test/export.test.py similarity index 69% rename from test/export.t rename to test/export.test.py index 7addc8566..777bcf227 100755 --- a/test/export.t +++ b/test/export.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -46,7 +45,7 @@ DATETIME_FORMAT = "%Y%m%dT%H%M%SZ" class TestExportCommand(TestCase): def setUp(self): self.t = Task() - self.t('add test') + self.t("add test") def export(self, id): code, out, err = self.t(("{0}".format(id), "rc.json.array=off", "export")) @@ -92,62 +91,62 @@ class TestExportCommand(TestCase): self.assertEqual(value, expected_value) def test_export_status(self): - self.assertString(self.export(1)['status'], "pending") + self.assertString(self.export(1)["status"], "pending") def test_export_uuid(self): - self.assertString(self.export(1)['uuid'], UUID_REGEXP, regexp=True) + self.assertString(self.export(1)["uuid"], UUID_REGEXP, regexp=True) def test_export_entry(self): - self.assertTimestamp(self.export(1)['entry']) + self.assertTimestamp(self.export(1)["entry"]) def test_export_description(self): - self.assertString(self.export(1)['description'], "test") + self.assertString(self.export(1)["description"], "test") def test_export_start(self): - self.t('1 start') - self.assertTimestamp(self.export(1)['start']) + self.t("1 start") + self.assertTimestamp(self.export(1)["start"]) def test_export_end(self): - self.t('1 start') + self.t("1 start") self.t.faketime("+5s") # After a task is "done" or "deleted", it does not have an ID by which # to filter it anymore. Add a tag to work around this. - self.t('1 done +workaround') - self.assertTimestamp(self.export('+workaround')['end']) + self.t("1 done +workaround") + self.assertTimestamp(self.export("+workaround")["end"]) def test_export_due(self): - self.t('1 modify due:today') - self.assertTimestamp(self.export(1)['due']) + self.t("1 modify due:today") + self.assertTimestamp(self.export(1)["due"]) def test_export_wait(self): - self.t('1 modify wait:tomorrow') - self.assertTimestamp(self.export(1)['wait']) + self.t("1 modify wait:tomorrow") + self.assertTimestamp(self.export(1)["wait"]) def test_export_modified(self): - self.assertTimestamp(self.export(1)['modified']) + self.assertTimestamp(self.export(1)["modified"]) def test_export_scheduled(self): - self.t('1 modify schedule:tomorrow') - self.assertTimestamp(self.export(1)['scheduled']) + self.t("1 modify schedule:tomorrow") + self.assertTimestamp(self.export(1)["scheduled"]) def test_export_recur(self): - self.t('1 modify recur:daily due:today') - self.assertString(self.export(1)['recur'], "daily") + self.t("1 modify recur:daily due:today") + self.assertString(self.export(1)["recur"], "daily") def test_export_project(self): - self.t('1 modify project:Home') - self.assertString(self.export(1)['project'], "Home") + self.t("1 modify project:Home") + self.assertString(self.export(1)["project"], "Home") def test_export_priority(self): - self.t('1 modify priority:H') - self.assertString(self.export(1)['priority'], "H") + self.t("1 modify priority:H") + self.assertString(self.export(1)["priority"], "H") def test_export_depends(self): - self.t(('add', 'everything depends on me task')) - self.t(('add', 'wrong, everything depends on me task')) - self.t('1 modify depends:2,3') + self.t(("add", "everything depends on me task")) + self.t(("add", "wrong, everything depends on me task")) + self.t("1 modify depends:2,3") - deps = self.export(1)['depends'] + deps = self.export(1)["depends"] self.assertType(deps, list) self.assertEqual(len(deps), 2) @@ -155,30 +154,30 @@ class TestExportCommand(TestCase): self.assertString(uuid, UUID_REGEXP, regexp=True) def test_export_urgency(self): - self.t('add urgent task +urgent') + self.t("add urgent task +urgent") # Urgency can be either integer or float - self.assertNumeric(self.export(1)['urgency']) + self.assertNumeric(self.export(1)["urgency"]) def test_export_numeric_uda(self): - self.t.config('uda.estimate.type', 'numeric') - self.t('add estimate:42 test numeric uda') - self.assertNumeric(self.export('2')['estimate'], 42) + self.t.config("uda.estimate.type", "numeric") + self.t("add estimate:42 test numeric uda") + self.assertNumeric(self.export("2")["estimate"], 42) def test_export_string_uda(self): - self.t.config('uda.estimate.type', 'string') - self.t('add estimate:big test string uda') - self.assertString(self.export('2')['estimate'], 'big') + self.t.config("uda.estimate.type", "string") + self.t("add estimate:big test string uda") + self.assertString(self.export("2")["estimate"], "big") def test_export_datetime_uda(self): - self.t.config('uda.estimate.type', 'date') - self.t('add estimate:eom test date uda') - self.assertTimestamp(self.export('2')['estimate']) + self.t.config("uda.estimate.type", "date") + self.t("add estimate:eom test date uda") + self.assertTimestamp(self.export("2")["estimate"]) def test_export_duration_uda(self): - self.t.config('uda.estimate.type', 'duration') - self.t('add estimate:month test duration uda') - self.assertString(self.export('2')['estimate'], 'P30D') + self.t.config("uda.estimate.type", "duration") + self.t("add estimate:month test duration uda") + self.assertString(self.export("2")["estimate"], "P30D") class TestExportCommandLimit(TestCase): @@ -187,8 +186,8 @@ class TestExportCommandLimit(TestCase): def test_export_obeys_limit(self): """Verify that 'task export limit:1' is obeyed""" - self.t('add one') - self.t('add two') + self.t("add one") + self.t("add two") code, out, err = self.t("/o/ limit:1 export") self.assertIn("one", out) @@ -197,6 +196,7 @@ class TestExportCommandLimit(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/feature.559.t b/test/feature.559.test.py similarity index 93% rename from test/feature.559.t rename to test/feature.559.test.py index df71de226..f637e104e 100755 --- a/test/feature.559.t +++ b/test/feature.559.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import re import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -68,11 +68,18 @@ class TestFeature559(TestCase): code, out, err = self.t.runError("rc.data.location=locationdoesnotexist list") self.assertNotIn("footask", out) self.assertNotIn("Error", out) - self.assertRegex(err, re.compile("Could not.+unable to open database file", re.DOTALL)) + self.assertRegex( + err, + re.compile( + "unable to open database file:.*Unable to open the database file", + re.DOTALL, + ), + ) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/feature.default.project.t b/test/feature.default.project.test.py similarity index 90% rename from test/feature.default.project.t rename to test/feature.default.project.test.py index 394ddda8a..558b081fe 100755 --- a/test/feature.default.project.t +++ b/test/feature.default.project.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -36,8 +36,8 @@ from basetest import Task, TestCase class TestDefaultProject(TestCase): - """Bug 1023: rc.default.project gets applied during modify, and should not - """ + """Bug 1023: rc.default.project gets applied during modify, and should not""" + def setUp(self): self.t = Task() @@ -46,8 +46,7 @@ class TestDefaultProject(TestCase): self.t.config("default.project", self.default_project) def test_with_project(self): - """default.project not applied when specified nor on attribute removal - """ + """default.project not applied when specified nor on attribute removal""" self.set_default_project() self.t("add foobar project:garden") @@ -55,7 +54,7 @@ class TestDefaultProject(TestCase): self.assertIn("foobar", out) - expected = "Project\s+garden" + expected = r"Project\s+garden" self.assertRegex(out, expected) self.t("1 modify project:") @@ -64,7 +63,7 @@ class TestDefaultProject(TestCase): self.assertIn("foobar", out) self.assertNotRegex(out, expected) - notexpected = "Project\s+" + self.default_project + notexpected = r"Project\s+" + self.default_project self.assertNotRegex(out, notexpected) def test_without_project(self): @@ -76,7 +75,7 @@ class TestDefaultProject(TestCase): self.assertIn("foobar", out) - expected = "Project\s+" + self.default_project + expected = r"Project\s+" + self.default_project self.assertRegex(out, expected) def test_default_project_inline_override(self): @@ -124,7 +123,7 @@ class TestDefaultProject(TestCase): self.t("1 annotate Hello") code, out, err = self.t("1 info") - expected = "Description\s+foobar\n[0-9-: ]+ Hello" + expected = "Description\\s+foobar\n[0-9-: ]+ Hello" self.assertRegex(out, expected) self.assertNotIn("Project", out) @@ -153,12 +152,12 @@ class TestDefaultProject(TestCase): self.set_default_project() DESC = "foobar" - self.t(('add', 'recur:daily', 'due:today', DESC)) + self.t(("add", "recur:daily", "due:today", DESC)) self.t() # Ensure creation of recurring children code, out, err = self.t("1 info") self.assertIn(DESC, out) - self.assertRegex(out, "Status\s+Recurring") # is a parent task + self.assertRegex(out, r"Status\s+Recurring") # is a parent task self.assertIn(self.default_project, out) self.t.faketime("+1d") @@ -173,8 +172,10 @@ class TestDefaultProject(TestCase): try: id = int(id) except ValueError: - raise ValueError("Unexpected output when running 'task count', " - "expected int, got '{0}'".format(id)) + raise ValueError( + "Unexpected output when running 'task count', " + "expected int, got '{0}'".format(id) + ) else: # parent task is not considered when counting id = str(id + 1) @@ -189,7 +190,7 @@ class TestDefaultProject(TestCase): """no project is applied on recurring tasks""" # NOTE - reported on TW-1279 DESC = "foobar" - self.t(('add', 'recur:daily', 'due:today', DESC)) + self.t(("add", "recur:daily", "due:today", DESC)) code, out, err = self.t() self.assertIn(DESC, out) @@ -205,13 +206,12 @@ class TestDefaultProject(TestCase): self.assertNotIn("Project", out) def test_recurring_with_project_and_default_project(self): - """default.project is not applied to children if parent has a project - """ + """default.project is not applied to children if parent has a project""" # NOTE - reported on TW-1279 self.set_default_project() DESC = "foobar" - self.t(('add', 'recur:daily', 'due:today', 'project:HELLO', DESC)) + self.t(("add", "recur:daily", "due:today", "project:HELLO", DESC)) code, out, err = self.t() self.assertIn(DESC, out) @@ -228,6 +228,7 @@ class TestDefaultProject(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/feature.print.empty.columns.t b/test/feature.print.empty.columns.test.py similarity index 99% rename from test/feature.print.empty.columns.t rename to test/feature.print.empty.columns.test.py index 09fb5cd1c..e68654a08 100755 --- a/test/feature.print.empty.columns.t +++ b/test/feature.print.empty.columns.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,7 +40,6 @@ class TestPrintEmptyColumns(TestCase): """Executed before each test in the class""" self.t = Task() - def test_empty_columns_feature(self): """Verify rc.print.empty.columns:yes shows more nothing than rc.print.empty.columns:no""" self.t("add one") @@ -65,6 +64,7 @@ class TestPrintEmptyColumns(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/feature.recurrence.t b/test/feature.recurrence.test.py similarity index 95% rename from test/feature.recurrence.t rename to test/feature.recurrence.test.py index fd97bc341..ea48bc724 100755 --- a/test/feature.recurrence.t +++ b/test/feature.recurrence.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -62,12 +62,13 @@ class TestRecurrenceProblems(TestCase): self.t("add foo due:today recur:yearly until:eom") code, out, err = self.t("info 1") - self.assertNotRegex(out, "Until\s+\d{10}") - self.assertRegex(out, "Until\s+\d+\/\d+\/\d{4}") + self.assertNotRegex(out, r"Until\s+\d{10}") + self.assertRegex(out, r"Until\s+\d+\/\d+\/\d{4}") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/feedback.t b/test/feedback.test.py similarity index 99% rename from test/feedback.t rename to test/feedback.test.py index 25f7fdfb9..6ab97eefd 100755 --- a/test/feedback.t +++ b/test/feedback.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -70,6 +70,7 @@ class TestFeature1013(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/filter.t b/test/filter.test.py similarity index 85% rename from test/filter.t rename to test/filter.test.py index 486943a0a..47e32eccd 100755 --- a/test/filter.t +++ b/test/filter.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import datetime + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -354,7 +354,7 @@ class TestFilterDue(TestCase): def setUp(self): self.t = Task() - self.t.config("due", "4") + self.t.config("due", "4") self.t.config("dateformat", "m/d/Y") just = datetime.datetime.now() + datetime.timedelta(days=3) @@ -442,7 +442,9 @@ class TestEmptyFilter(TestCase): self.t("add foo") self.t("add bar") - code, out, err = self.t.runError("modify rc.allow.empty.filter=yes rc.confirmation=no priority:H") + code, out, err = self.t.runError( + "modify rc.allow.empty.filter=yes rc.confirmation=no priority:H" + ) self.assertIn("Command prevented from running.", err) def test_empty_filter_error(self): @@ -452,7 +454,10 @@ class TestEmptyFilter(TestCase): self.t("add bar") code, out, err = self.t.runError("modify rc.allow.empty.filter=no priority:H") - self.assertIn("You did not specify a filter, and with the 'allow.empty.filter' value, no action is taken.", err) + self.assertIn( + "You did not specify a filter, and with the 'allow.empty.filter' value, no action is taken.", + err, + ) class TestFilterPrefix(TestCase): @@ -462,86 +467,86 @@ class TestFilterPrefix(TestCase): cls.t = Task() cls.t.config("verbose", "nothing") - cls.t('add project:foo.uno priority:H +tag "one foo"' ) - cls.t('add project:foo.dos priority:H "two"' ) - cls.t('add project:foo.tres "three"' ) - cls.t('add project:bar.uno priority:H "four"' ) - cls.t('add project:bar.dos +tag "five"' ) - cls.t('add project:bar.tres "six foo"' ) + cls.t('add project:foo.uno priority:H +tag "one foo"') + cls.t('add project:foo.dos priority:H "two"') + cls.t('add project:foo.tres "three"') + cls.t('add project:bar.uno priority:H "four"') + cls.t('add project:bar.dos +tag "five"') + cls.t('add project:bar.tres "six foo"') cls.t('add project:bazuno "seven bar foo"') cls.t('add project:bazdos "eight bar foo"') def test_list_all(self): """No filter shows all tasks.""" - code, out, err = self.t('list') - self.assertIn('one', out) - self.assertIn('two', out) - self.assertIn('three', out) - self.assertIn('four', out) - self.assertIn('five', out) - self.assertIn('six', out) - self.assertIn('seven', out) - self.assertIn('eight', out) + code, out, err = self.t("list") + self.assertIn("one", out) + self.assertIn("two", out) + self.assertIn("three", out) + self.assertIn("four", out) + self.assertIn("five", out) + self.assertIn("six", out) + self.assertIn("seven", out) + self.assertIn("eight", out) def test_list_project_foo(self): """Filter on project name.""" - code, out, err = self.t('list project:foo') - self.assertIn('one', out) - self.assertIn('two', out) - self.assertIn('three', out) - self.assertNotIn('four', out) - self.assertNotIn('five', out) - self.assertNotIn('six', out) - self.assertNotIn('seven', out) - self.assertNotIn('eight', out) + code, out, err = self.t("list project:foo") + self.assertIn("one", out) + self.assertIn("two", out) + self.assertIn("three", out) + self.assertNotIn("four", out) + self.assertNotIn("five", out) + self.assertNotIn("six", out) + self.assertNotIn("seven", out) + self.assertNotIn("eight", out) def test_list_project_not_foo(self): """Filter on not project name.""" - code, out, err = self.t('list project.not:foo') - self.assertNotIn('one', out) - self.assertNotIn('two', out) - self.assertNotIn('three', out) - self.assertIn('four', out) - self.assertIn('five', out) - self.assertIn('six', out) - self.assertIn('seven', out) - self.assertIn('eight', out) + code, out, err = self.t("list project.not:foo") + self.assertNotIn("one", out) + self.assertNotIn("two", out) + self.assertNotIn("three", out) + self.assertIn("four", out) + self.assertIn("five", out) + self.assertIn("six", out) + self.assertIn("seven", out) + self.assertIn("eight", out) def test_list_project_startswith_bar(self): """Filter on project name start.""" - code, out, err = self.t('list project.startswith:bar') - self.assertNotIn('one', out) - self.assertNotIn('two', out) - self.assertNotIn('three', out) - self.assertIn('four', out) - self.assertIn('five', out) - self.assertIn('six', out) - self.assertNotIn('seven', out) - self.assertNotIn('eight', out) + code, out, err = self.t("list project.startswith:bar") + self.assertNotIn("one", out) + self.assertNotIn("two", out) + self.assertNotIn("three", out) + self.assertIn("four", out) + self.assertIn("five", out) + self.assertIn("six", out) + self.assertNotIn("seven", out) + self.assertNotIn("eight", out) def test_list_project_ba(self): """Filter on project partial match.""" - code, out, err = self.t('list project:ba') - self.assertNotIn('one', out) - self.assertNotIn('two', out) - self.assertNotIn('three', out) - self.assertIn('four', out) - self.assertIn('five', out) - self.assertIn('six', out) - self.assertIn('seven', out) - self.assertIn('eight', out) + code, out, err = self.t("list project:ba") + self.assertNotIn("one", out) + self.assertNotIn("two", out) + self.assertNotIn("three", out) + self.assertIn("four", out) + self.assertIn("five", out) + self.assertIn("six", out) + self.assertIn("seven", out) + self.assertIn("eight", out) def test_list_description_has_foo(self): """Filter on description pattern.""" - code, out, err = self.t('list description.has:foo') - self.assertIn('one', out) - self.assertNotIn('two', out) - self.assertNotIn('three', out) - self.assertNotIn('four', out) - self.assertNotIn('five', out) - self.assertIn('six', out) - self.assertIn('seven', out) - self.assertIn('eight', out) + code, out, err = self.t("list description.has:foo") + self.assertIn("one", out) + self.assertNotIn("two", out) + self.assertNotIn("three", out) + self.assertNotIn("four", out) + self.assertNotIn("five", out) + self.assertIn("six", out) + self.assertIn("seven", out) + self.assertIn("eight", out) class TestBug480B(TestCase): @@ -676,7 +681,7 @@ class TestBug1600(TestCase): self.assertNotIn("foobar2", out) def test_filter_question_in_descriptions(self): - """filter - description contains ? """ + """filter - description contains ?""" self.t("add foobar1") self.t("add foo?bar") @@ -689,7 +694,7 @@ class TestBug1600(TestCase): self.assertNotIn("foobar1", out) def test_filter_brackets_in_descriptions(self): - """filter - description contains [] """ + """filter - description contains []""" self.t("add [foobar1]") self.t("add [foobar2]") @@ -708,9 +713,9 @@ class TestBug1656(TestCase): def test_report_filter_parenthesized(self): """default report filter parenthesized""" - self.t('add task1 +work') - self.t('add task2 +work') - self.t('1 done') + self.t("add task1 +work") + self.t("add task2 +work") + self.t("1 done") # Sanity check, next does not display completed tasks code, out, err = self.t("next") @@ -748,19 +753,19 @@ class TestHasHasnt(TestCase): def test_has_hasnt(self): """Verify the 'has' and 'hasnt' attribute modifiers""" - self.t("add foo") # 1 - self.t("add foo") # 2 + self.t("add foo") # 1 + self.t("add foo") # 2 self.t("2 annotate bar") - self.t("add foo") # 3 + self.t("add foo") # 3 self.t("3 annotate bar") self.t("3 annotate baz") - self.t("add bar") # 4 - self.t("add bar") # 5 + self.t("add bar") # 4 + self.t("add bar") # 5 self.t("5 annotate foo") - self.t("add bar") # 6 + self.t("add bar") # 6 self.t("6 annotate foo") self.t("6 annotate baz") - self.t("add one") # 7 + self.t("add one") # 7 self.t("7 annotate two") self.t("7 annotate three") @@ -788,8 +793,8 @@ class TestBefore(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t('add foo entry:2008-12-22 start:2008-12-22') - cls.t('add bar entry:2009-04-17 start:2009-04-17') + cls.t("add foo entry:2008-12-22 start:2008-12-22") + cls.t("add bar entry:2009-04-17 start:2009-04-17") def test_correctly_recorded_start(self): """Verify start dates properly recorded""" @@ -841,14 +846,14 @@ class TestBy(TestCase): self.t = Task() def test_by_eoy_includes_eoy(self): - """ Verify by-end-of-year includes task due *at* end-of-year """ + """Verify by-end-of-year includes task due *at* end-of-year""" self.t("add zero due:eoy") code, out, err = self.t("due.by:eoy") self.assertIn("zero", out) def test_by_tomorrow_includes_tomorrow(self): - """ Verify that by-tomorrow also includes tomorrow itself """ + """Verify that by-tomorrow also includes tomorrow itself""" self.t.faketime("2021-07-16 21:00:00") self.t("add zero due:2021-07-17") @@ -856,7 +861,7 @@ class TestBy(TestCase): self.assertIn("zero", out) def test_by_yesterday_does_not_include_today(self): - """ Verify that by-yesterday does not include today """ + """Verify that by-yesterday does not include today""" self.t("add zero") code, out, err = self.t.runError("entry.by:yesterday") @@ -870,8 +875,8 @@ class Test1424(TestCase): def test_1824_days(self): """1424: Check that due:1824d works""" - self.t('add foo due:1824d') - code, out, err = self.t('_get 1.due.year') + self.t("add foo due:1824d") + code, out, err = self.t("_get 1.due.year") # NOTE This test has a possible race condition when run "during" EOY. # If Taskwarrior is executed at 23:59:59 on new year's eve and the # python code below runs at 00:00:00 on new year's day, the two will @@ -882,8 +887,8 @@ class Test1424(TestCase): def test_3648_days(self): """1424: Check that due:3648d works""" - self.t('add foo due:3648d') - code, out, err = self.t('_get 1.due.year') + self.t("add foo due:3648d") + code, out, err = self.t("_get 1.due.year") # NOTE This test has a possible race condition when run "during" EOY. # If Taskwarrior is executed at 23:59:59 on new year's eve and the # python code below runs at 00:00:00 on new year's day, the two will @@ -898,22 +903,22 @@ class Test1424(TestCase): class Test1452(TestCase): def setUp(self): self.t = Task() - self.t('add task') - self.task_uuid = self.t.export_one()['uuid'] + self.t("add task") + self.task_uuid = self.t.export_one()["uuid"] def test_get_task_by_uuid_with_prefix(self): """1452: Tries to filter task simply by its uuid, using uuid: prefix.""" - output = self.t.export_one('uuid:%s' % self.task_uuid) + output = self.t.export_one("uuid:%s" % self.task_uuid) # Sanity check it is the correct one - self.assertEqual(output['uuid'], self.task_uuid) + self.assertEqual(output["uuid"], self.task_uuid) def test_get_task_by_uuid_without_prefix(self): """1452: Tries to filter task simply by its uuid, without using uuid: prefix.""" output = self.t.export_one(self.task_uuid) # Sanity check it is the correct one - self.assertEqual(output['uuid'], self.task_uuid) + self.assertEqual(output["uuid"], self.task_uuid) class TestBug1456(TestCase): @@ -936,22 +941,22 @@ class TestBug1456(TestCase): class Test1468(TestCase): def setUp(self): self.t = Task() - self.t('add project:home buy milk') - self.t('add project:home mow the lawn') + self.t("add project:home buy milk") + self.t("add project:home mow the lawn") def test_single_attribute_filter(self): """1468: Single attribute filter (project:home)""" - code, out, err = self.t('list project:home') + code, out, err = self.t("list project:home") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('buy milk', out) - self.assertIn('mow the lawn', out) + self.assertIn("buy milk", out) + self.assertIn("mow the lawn", out) def test_attribute_and_search_filter(self): """1468: Attribute and implicit search filter (project:home /lawn/)""" - code, out, err = self.t('list project:home /lawn/') + code, out, err = self.t("list project:home /lawn/") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertNotIn('buy milk', out) - self.assertIn('mow the lawn', out) + self.assertNotIn("buy milk", out) + self.assertIn("mow the lawn", out) class TestBug1521(TestCase): @@ -1022,19 +1027,19 @@ class Test1634(TestCase): self.t = Task() # Setup some tasks due on 2015-07-07 - self.t('add due:2015-07-07T00:00:00 ON1') - self.t('add due:2015-07-07T14:34:56 ON2') - self.t('add due:2015-07-07T23:59:59 ON3') + self.t("add due:2015-07-07T00:00:00 ON1") + self.t("add due:2015-07-07T14:34:56 ON2") + self.t("add due:2015-07-07T23:59:59 ON3") # Setup some tasks not due on 2015-07-07 - self.t('add due:2015-07-06T23:59:59 OFF4') - self.t('add due:2015-07-08T00:00:00 OFF5') - self.t('add due:2015-07-08T00:00:01 OFF6') - self.t('add due:2015-07-06T00:00:00 OFF7') + self.t("add due:2015-07-06T23:59:59 OFF4") + self.t("add due:2015-07-08T00:00:00 OFF5") + self.t("add due:2015-07-08T00:00:01 OFF6") + self.t("add due:2015-07-06T00:00:00 OFF7") def test_due_match_not_exact(self): """1634: Test that due: matches any task that date.""" - code, out, err = self.t('due:2015-07-07 minimal') + code, out, err = self.t("due:2015-07-07 minimal") # Asswer that only tasks ON the date are listed. self.assertIn("ON1", out) @@ -1049,7 +1054,7 @@ class Test1634(TestCase): def test_due_not_match_not_exact(self): """1634: Test that due.not: does not match any task that date.""" - code, out, err = self.t('due.not:2015-07-07 minimal') + code, out, err = self.t("due.not:2015-07-07 minimal") # Assert that task ON the date are not listed. self.assertNotIn("ON1", out) @@ -1073,14 +1078,18 @@ class TestBug1915(TestCase): def test_complex_and_or_query_variant_one(self): """1915: Make sure parser handles complex and-or queries correctly (1)""" - code, out, err = self.t("rc.verbose:nothing '(project:A or project:B) and status:pending' all") + code, out, err = self.t( + "rc.verbose:nothing '(project:A or project:B) and status:pending' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) def test_complex_and_or_query_variant_two(self): """1915: Make sure parser handles complex and-or queries correctly (2)""" - code, out, err = self.t("rc.verbose:nothing '( project:A or project:B ) and status:pending' all") + code, out, err = self.t( + "rc.verbose:nothing '( project:A or project:B ) and status:pending' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) @@ -1088,7 +1097,9 @@ class TestBug1915(TestCase): @unittest.expectedFailure def test_complex_and_or_query_variant_three(self): """1915: Make sure parser handles complex and-or queries correctly (3)""" - code, out, err = self.t("rc.verbose:nothing 'status:pending and (project:A or project:B)' all") + code, out, err = self.t( + "rc.verbose:nothing 'status:pending and (project:A or project:B)' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) @@ -1096,28 +1107,36 @@ class TestBug1915(TestCase): @unittest.expectedFailure def test_complex_and_or_query_variant_four(self): """1915: Make sure parser handles complex and-or queries correctly (4)""" - code, out, err = self.t("rc.verbose:nothing 'status:pending and ( project:A or project:B )' all") + code, out, err = self.t( + "rc.verbose:nothing 'status:pending and ( project:A or project:B )' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) def test_complex_and_or_query_variant_five(self): """1915: Make sure parser handles complex and-or queries correctly (5)""" - code, out, err = self.t("rc.verbose:nothing status:pending and '(project:A or project:B)' all") + code, out, err = self.t( + "rc.verbose:nothing status:pending and '(project:A or project:B)' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) def test_complex_and_or_query_variant_six(self): """1915: Make sure parser handles complex and-or queries correctly (6)""" - code, out, err = self.t("rc.verbose:nothing status:pending and '( project:A or project:B )' all") + code, out, err = self.t( + "rc.verbose:nothing status:pending and '( project:A or project:B )' all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) def test_complex_and_or_query_variant_seven(self): """1915: Make sure parser handles complex and-or queries correctly (7)""" - code, out, err = self.t("rc.verbose:nothing status:pending and \\( project:A or project:B \\) all") + code, out, err = self.t( + "rc.verbose:nothing status:pending and \\( project:A or project:B \\) all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) @@ -1125,7 +1144,9 @@ class TestBug1915(TestCase): @unittest.expectedFailure def test_complex_and_or_query_variant_eight(self): """1915: Make sure parser handles complex and-or queries correctly (8)""" - code, out, err = self.t("rc.verbose:nothing status:pending and \\(project:A or project:B\\) all") + code, out, err = self.t( + "rc.verbose:nothing status:pending and \\(project:A or project:B\\) all" + ) self.assertIn("thingA", out) self.assertIn("thingB", out) self.assertNotIn("thingC", out) @@ -1137,11 +1158,11 @@ class Test2577(TestCase): def test_filtering_for_datetime_like(self): """2577: Check that filtering for datetime-like project names works""" - self.t('add one pro:sat') # looks like "saturday" - self.t('add two pro:whatever') + self.t("add one pro:sat") # looks like "saturday" + self.t("add two pro:whatever") # This should not fail (fails on 2.5.3) - code, out, err = self.t('pro:sat') + code, out, err = self.t("pro:sat") # Assert expected output, but the crucial part of this test is success # of the call above @@ -1150,6 +1171,7 @@ class Test2577(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/fontunderline.t b/test/fontunderline.test.py similarity index 77% rename from test/fontunderline.t rename to test/fontunderline.test.py index 76721d639..626d6e1c0 100755 --- a/test/fontunderline.t +++ b/test/fontunderline.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -58,40 +58,57 @@ class TestUnderline(TestCase): # * When isatty (fileno (stdout)) is false, color is automatically disabled. def test_nocolor_noforce_nounderline(self): - code, out, err = self.t("1 info rc.color:off rc._forcecolor:off rc.fontunderline:off") + code, out, err = self.t( + "1 info rc.color:off rc._forcecolor:off rc.fontunderline:off" + ) self.assertIn("--------", out) def test_nocolor_noforce_underline(self): - code, out, err = self.t("1 info rc.color:off rc._forcecolor:off rc.fontunderline:on") + code, out, err = self.t( + "1 info rc.color:off rc._forcecolor:off rc.fontunderline:on" + ) self.assertIn("--------", out) def test_nocolor_force_nounderline(self): - code, out, err = self.t("1 info rc.color:off rc._forcecolor:on rc.fontunderline:off") + code, out, err = self.t( + "1 info rc.color:off rc._forcecolor:on rc.fontunderline:off" + ) self.assertIn("--------", out) def test_nocolor_force_underline(self): - code, out, err = self.t("1 info rc.color:off rc._forcecolor:on rc.fontunderline:on") + code, out, err = self.t( + "1 info rc.color:off rc._forcecolor:on rc.fontunderline:on" + ) self.assertNotIn("--------", out) def test_color_noforce_nounderline(self): - code, out, err = self.t("1 info rc.color:on rc._forcecolor:off rc.fontunderline:off") + code, out, err = self.t( + "1 info rc.color:on rc._forcecolor:off rc.fontunderline:off" + ) self.assertIn("--------", out) def test_color_noforce_underline(self): - code, out, err = self.t("1 info rc.color:on rc._forcecolor:off rc.fontunderline:on") + code, out, err = self.t( + "1 info rc.color:on rc._forcecolor:off rc.fontunderline:on" + ) self.assertIn("--------", out) def test_color_force_nounderline(self): - code, out, err = self.t("1 info rc.color:on rc._forcecolor:on rc.fontunderline:off") + code, out, err = self.t( + "1 info rc.color:on rc._forcecolor:on rc.fontunderline:off" + ) self.assertIn("--------", out) def test_color_force_underline(self): - code, out, err = self.t("1 info rc.color:on rc._forcecolor:on rc.fontunderline:on") + code, out, err = self.t( + "1 info rc.color:on rc._forcecolor:on rc.fontunderline:on" + ) self.assertNotIn("--------", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/format.t b/test/format.test.py similarity index 85% rename from test/format.t rename to test/format.test.py index 30b192292..b1cf99c95 100755 --- a/test/format.t +++ b/test/format.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import math + # Ensure python finds the local simpletap and basetest modules sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -62,11 +62,11 @@ class TestCountdown(TestCase): def test_countdown_up(self): """Verify countdown sorting: ascending""" - self.t.config("report.up.description", "countdown+ report") - self.t.config("report.up.columns", "id,due.countdown,description") - self.t.config("report.up.labels", "ID,Countdown,Description") - self.t.config("report.up.filter", "status:pending") - self.t.config("report.up.sort", "due+") + self.t.config("report.up.description", "countdown+ report") + self.t.config("report.up.columns", "id,due.countdown,description") + self.t.config("report.up.labels", "ID,Countdown,Description") + self.t.config("report.up.filter", "status:pending") + self.t.config("report.up.sort", "due+") code, out, err = self.t("up") self.assertRegex(out, " one\n.+ two\n") @@ -86,11 +86,11 @@ class TestCountdown(TestCase): def test_countdown_down(self): """Verify countdown sorting: descending""" - self.t.config("report.down.description", "countdown- report") - self.t.config("report.down.columns", "id,due.countdown,description") - self.t.config("report.down.labels", "ID,Countdown,Description") - self.t.config("report.down.filter", "status:pending") - self.t.config("report.down.sort", "due-") + self.t.config("report.down.description", "countdown- report") + self.t.config("report.down.columns", "id,due.countdown,description") + self.t.config("report.down.labels", "ID,Countdown,Description") + self.t.config("report.down.filter", "status:pending") + self.t.config("report.down.sort", "due-") code, out, err = self.t("down") self.assertRegex(out, " fifteen\n.+ fourteen\n") @@ -118,12 +118,12 @@ class TestFormatDepends(TestCase): def test_depends_default(self): self.t.config("report.formatdep.columns", "description,depends") code, out, err = self.t("formatdep") - self.assertRegex(out, "one\s+1") + self.assertRegex(out, r"one\s+1") def test_depends_count(self): self.t.config("report.formatdep.columns", "description,depends.count") code, out, err = self.t("formatdep") - self.assertRegex(out, "one\s+\[1\]") + self.assertRegex(out, r"one\s+\[1\]") class TestBug101(TestCase): @@ -144,7 +144,9 @@ class TestBug101(TestCase): self.short_description = "A_task_description_" # Generate long string - self.long_description = self.short_description * int(math.ceil(float(self.width)/len(self.short_description))) + self.long_description = self.short_description * int( + math.ceil(float(self.width) / len(self.short_description)) + ) def test_short_no_count(self): """101: Check short description with no annotations""" @@ -165,7 +167,7 @@ class TestBug101(TestCase): """101: Check long description with no annotations""" self.t(("add", self.long_description)) code, out, err = self.t("bug101") - expected = self.long_description[:(self.width - 3)] + "..." + expected = self.long_description[: (self.width - 3)] + "..." self.assertIn(expected, out) def test_long_with_count(self): @@ -173,7 +175,7 @@ class TestBug101(TestCase): self.t(("add", self.long_description)) self.t("1 annotate 'A task annotation'") code, out, err = self.t("bug101") - expected = self.long_description[:(self.width - 7)] + "... [1]" + expected = self.long_description[: (self.width - 7)] + "... [1]" self.assertIn(expected, out) def test_long_with_double_digit_count(self): @@ -182,12 +184,13 @@ class TestBug101(TestCase): for i in range(10): self.t("1 annotate 'A task annotation'") code, out, err = self.t("bug101") - expected = self.long_description[:(self.width - 8)] + "... [10]" + expected = self.long_description[: (self.width - 8)] + "... [10]" self.assertIn(expected, out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/gc.t b/test/gc.test.py similarity index 91% rename from test/gc.t rename to test/gc.test.py index 5b38e1f86..2b3c0577e 100755 --- a/test/gc.t +++ b/test/gc.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -50,7 +50,7 @@ class TestGC(TestCase): self.t.config("gc", "0") self.t("1 done") code, out, err = self.t("gctest") - self.assertRegex(out, "1\s+one", "should still have ID") + self.assertRegex(out, r"1\s+one", "should still have ID") def test_gc_off_mod(self): """mod by ID after done with gc off""" @@ -59,7 +59,7 @@ class TestGC(TestCase): self.t("gctest") self.t("2 mod +TWO") code, out, err = self.t("gctest") - self.assertRegex(out, "2\s+two\s+TWO", "modified 'two'") + self.assertRegex(out, r"2\s+two\s+TWO", "modified 'two'") def test_gc_on_id(self): """IDs reshuffle after report when GC on""" @@ -67,12 +67,13 @@ class TestGC(TestCase): self.t("1 done") self.t("2 mod +TWO") code, out, err = self.t("gctest") - self.assertRegex(out, "1\s+two\s+TWO") - self.assertRegex(out, "2\s+three") + self.assertRegex(out, r"1\s+two\s+TWO") + self.assertRegex(out, r"2\s+three") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/helpers.t b/test/helpers.test.py similarity index 96% rename from test/helpers.t rename to test/helpers.test.py index db8222dce..b28c8f6c8 100755 --- a/test/helpers.t +++ b/test/helpers.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,10 +43,10 @@ class TestZshAttributes(TestCase): def test_zsh_attributes_helper(self): """Ensure the _zshattributes command returns the expected format""" code, out, err = self.t("_zshattributes") - for line in out.split('\n'): - if line != '': - fields = line.split(':') - self.assertEqual(fields[0], fields[1]) + for line in out.split("\n"): + if line != "": + fields = line.split(":") + self.assertEqual(fields[0], fields[1]) class TestZshCompletion(TestCase): @@ -70,6 +70,7 @@ class TestAliasesCompletion(TestCase): """Aliases should be listed by '_aliases' not '_commands' or '_zshcommands' reported as bug 1043 """ + def setUp(self): self.t = Task() self.t.config("alias.samplealias", "long") @@ -132,6 +133,7 @@ class TestBug956(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/history.t b/test/history.test.py similarity index 86% rename from test/history.t rename to test/history.test.py index 1232eaf65..e2c5a7c40 100755 --- a/test/history.t +++ b/test/history.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,11 +28,13 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase + class TestHistoryDaily(TestCase): def setUp(self): """Executed before each test in the class""" @@ -55,17 +56,18 @@ class TestHistoryDaily(TestCase): def test_history_daily(self): """Verify 'history.daily' correctly categorizes data""" code, out, err = self.t("history.daily") - self.assertRegex(out, "7\s+1\s+0\s+6") - self.assertRegex(out, "2\s+3\s+3\s+-4") - self.assertRegex(out, "4\s+2\s+1\s+1") + self.assertRegex(out, r"7\s+1\s+0\s+6") + self.assertRegex(out, r"2\s+3\s+3\s+-4") + self.assertRegex(out, r"4\s+2\s+1\s+1") code, out, err = self.t("ghistory.daily rc._forcecolor:on") - self.assertRegex(out, "\s7.+\s1.+") - self.assertRegex(out, "\s2.+\s3.+\s3.+") + self.assertRegex(out, r"\s7.+\s1.+") + self.assertRegex(out, r"\s2.+\s3.+\s3.+") code, out, err = self.t("ghistory.daily") - self.assertRegex(out, "2015\s+January\s+2\s+\++X+\s") - self.assertRegex(out, "\s+February\s+2\s+\++X+\-+") + self.assertRegex(out, r"2015\s+January\s+2\s+\++X+\s") + self.assertRegex(out, r"\s+February\s+2\s+\++X+\-+") + class TestHistoryWeekly(TestCase): def setUp(self): @@ -88,17 +90,17 @@ class TestHistoryWeekly(TestCase): def test_history_weekly(self): """Verify 'history.weekly' correctly categorizes data""" code, out, err = self.t("history.weekly") - self.assertRegex(out, "7\s+1\s+0\s+6") - self.assertRegex(out, "2\s+3\s+3\s+-4") - self.assertRegex(out, "4\s+2\s+1\s+1") + self.assertRegex(out, r"7\s+1\s+0\s+6") + self.assertRegex(out, r"2\s+3\s+3\s+-4") + self.assertRegex(out, r"4\s+2\s+1\s+1") code, out, err = self.t("ghistory.weekly rc._forcecolor:on") - self.assertRegex(out, "\s7.+\s1.+") - self.assertRegex(out, "\s2.+\s3.+\s3.+") + self.assertRegex(out, r"\s7.+\s1.+") + self.assertRegex(out, r"\s2.+\s3.+\s3.+") code, out, err = self.t("ghistory.weekly") - self.assertRegex(out, "2014\s+December\s+28\s+\++X+\s") - self.assertRegex(out, "2015\s+February\s+1\s+\++X+\-+") + self.assertRegex(out, r"2014\s+December\s+28\s+\++X+\s") + self.assertRegex(out, r"2015\s+February\s+1\s+\++X+\-+") class TestHistoryMonthly(TestCase): @@ -122,17 +124,17 @@ class TestHistoryMonthly(TestCase): def test_history_monthly(self): """Verify 'history.monthly' correctly categorizes data""" code, out, err = self.t("history.monthly") - self.assertRegex(out, "7\s+1\s+0\s+6") - self.assertRegex(out, "2\s+3\s+3\s+-4") - self.assertRegex(out, "4\s+2\s+1\s+1") + self.assertRegex(out, r"7\s+1\s+0\s+6") + self.assertRegex(out, r"2\s+3\s+3\s+-4") + self.assertRegex(out, r"4\s+2\s+1\s+1") code, out, err = self.t("ghistory.monthly rc._forcecolor:on") - self.assertRegex(out, "\s7.+\s1.+") - self.assertRegex(out, "\s2.+\s3.+\s3.+") + self.assertRegex(out, r"\s7.+\s1.+") + self.assertRegex(out, r"\s2.+\s3.+\s3.+") code, out, err = self.t("ghistory.monthly") - self.assertRegex(out, "2015\s+January\s+\++X+\s") - self.assertRegex(out, "\s+February\s+\++X+\-+") + self.assertRegex(out, r"2015\s+January\s+\++X+\s") + self.assertRegex(out, r"\s+February\s+\++X+\-+") class TestHistoryAnnual(TestCase): @@ -160,20 +162,22 @@ class TestHistoryAnnual(TestCase): def test_history_annual(self): """Verify 'history.annual' correctly categorizes data""" code, out, err = self.t("history.annual") - self.assertRegex(out, "7\s+1\s+0\s+6") - self.assertRegex(out, "2\s+3\s+3\s+-4") - self.assertRegex(out, "4\s+2\s+1\s+1") + self.assertRegex(out, r"7\s+1\s+0\s+6") + self.assertRegex(out, r"2\s+3\s+3\s+-4") + self.assertRegex(out, r"4\s+2\s+1\s+1") code, out, err = self.t("ghistory.annual rc._forcecolor:on") - self.assertRegex(out, "\s7.+\s1.+") - self.assertRegex(out, "\s2.+\s3.+\s3.+") + self.assertRegex(out, r"\s7.+\s1.+") + self.assertRegex(out, r"\s2.+\s3.+\s3.+") code, out, err = self.t("ghistory.annual") - self.assertRegex(out, "2014\s+\++X+\s") - self.assertRegex(out, "2015\s+\++X+\-+") + self.assertRegex(out, r"2014\s+\++X+\s") + self.assertRegex(out, r"2015\s+\++X+\-+") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hooks.env.t b/test/hooks.env.test.py similarity index 80% rename from test/hooks.env.t rename to test/hooks.env.test.py index 41562bf2d..14eda273d 100755 --- a/test/hooks.env.t +++ b/test/hooks.env.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,28 +43,30 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_env(self): """on-launch-env - a well-behaved, successful, on-launch hook that echoes its env.""" - hookname = 'on-launch-good-env' + hookname = "on-launch-good-env" self.t.hooks.add_default(hookname, log=True) - code, out, err = self.t("version") # Arbitrary command that generates output. + code, out, err = self.t("version") # Arbitrary command that generates output. hook = self.t.hooks[hookname] hook.assertTriggeredCount(1) hook.assertExitcode(0) logs = hook.get_logs() - taskenv = {k:v for k, v in (line.split(":", 1) for line in logs["output"]["msgs"])} + taskenv = { + k: v for k, v in (line.split(":", 1) for line in logs["output"]["msgs"]) + } - self.assertEqual('api' in taskenv, True, 'api:...') - self.assertEqual('args' in taskenv, True, 'args:...') - self.assertEqual('command' in taskenv, True, 'command:...') - self.assertEqual('rc' in taskenv, True, 'rc:...') - self.assertEqual('data' in taskenv, True, 'data:...') - self.assertEqual('version' in taskenv, True, 'version:...') + self.assertEqual("api" in taskenv, True, "api:...") + self.assertEqual("args" in taskenv, True, "args:...") + self.assertEqual("command" in taskenv, True, "command:...") + self.assertEqual("rc" in taskenv, True, "rc:...") + self.assertEqual("data" in taskenv, True, "data:...") + self.assertEqual("version" in taskenv, True, "version:...") def test_onlaunch_builtin_env_diag(self): """Verify that 'diagnostics' can see hook details""" - hookname = 'on-launch-good-env' + hookname = "on-launch-good-env" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("diagnostics") @@ -72,7 +74,7 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_env_debug(self): """Verify that 'debug.hooks' shows hook details""" - hookname = 'on-launch-good-env' + hookname = "on-launch-good-env" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("version rc.debug.hooks:2") @@ -85,6 +87,7 @@ class TestHooksOnLaunch(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hooks.on-add.t b/test/hooks.on-add.test.py similarity index 81% rename from test/hooks.on-add.t rename to test/hooks.on-add.test.py index d62c0d109..011ebd86f 100755 --- a/test/hooks.on-add.t +++ b/test/hooks.on-add.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,7 +43,7 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_accept(self): """on-add-accept - a well-behaved, successful, on-add hook.""" - hookname = 'on-add-accept' + hookname = "on-add-accept" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -58,9 +58,26 @@ class TestHooksOnAdd(TestCase): code, out, err = self.t("1 info") self.assertIn("Description foo", out) + def test_onadd_builtin_accept_modify(self): + """on-add-accept-modify - a well-behaved, successful, on-add hook, that modifies the added task.""" + hookname = "on-add-modify" + self.t.hooks.add_default(hookname, log=True) + + code, out, err = self.t("add teh foo") + + hook = self.t.hooks[hookname] + hook.assertTriggeredCount(1) + hook.assertExitcode(0) + + logs = hook.get_logs() + self.assertEqual(logs["output"]["msgs"][0], "FEEDBACK") + + code, out, err = self.t("1 info") + self.assertIn("Description the foo", out) + def test_onadd_builtin_reject(self): """on-add-reject - a well-behaved, failing, on-add hook.""" - hookname = 'on-add-reject' + hookname = "on-add-reject" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") @@ -72,9 +89,13 @@ class TestHooksOnAdd(TestCase): logs = hook.get_logs() self.assertEqual(logs["output"]["msgs"][0], "FEEDBACK") + # task was not added + code, out, err = self.t.runError("1 info") + self.assertIn("No matches", err) + def test_onadd_builtin_misbehave1(self): """on-add-misbehave1 - does not consume input.""" - hookname = 'on-add-misbehave1' + hookname = "on-add-misbehave1" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") @@ -88,7 +109,7 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_misbehave2(self): """on-add-misbehave2 - does not emit JSON.""" - hookname = 'on-add-misbehave2' + hookname = "on-add-misbehave2" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") @@ -100,7 +121,7 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_misbehave3(self): """on-add-misbehave3 - emits additional JSON.""" - hookname = 'on-add-misbehave3' + hookname = "on-add-misbehave3" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") @@ -112,7 +133,7 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_misbehave4(self): """on-add-misbehave4 - emits different task JSON.""" - hookname = 'on-add-misbehave4' + hookname = "on-add-misbehave4" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") @@ -127,11 +148,11 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_misbehave5(self): """on-add-misbehave5 - emits syntactically wrong JSON.""" - hookname = 'on-add-misbehave5' + hookname = "on-add-misbehave5" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") - self.assertIn("Hook Error: JSON syntax error in: {\"}", err) + self.assertIn('Hook Error: JSON syntax error in: {"}', err) hook = self.t.hooks[hookname] hook.assertTriggeredCount(1) @@ -143,11 +164,14 @@ class TestHooksOnAdd(TestCase): def test_onadd_builtin_misbehave6(self): """on-add-misbehave6 - emits incomplete JSON.""" - hookname = 'on-add-misbehave6' + hookname = "on-add-misbehave6" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t.runError("add foo") - self.assertIn("Hook Error: JSON Object missing 'uuid' attribute from hook script: on-add-misbehave6", err) + self.assertIn( + "Hook Error: JSON Object missing 'uuid' attribute from hook script: on-add-misbehave6", + err, + ) hook = self.t.hooks[hookname] hook.assertTriggeredCount(1) @@ -156,8 +180,10 @@ class TestHooksOnAdd(TestCase): logs = hook.get_logs() self.assertEqual(logs["output"]["msgs"][0], "FEEDBACK") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hooks.on-exit.t b/test/hooks.on-exit.test.py similarity index 95% rename from test/hooks.on-exit.t rename to test/hooks.on-exit.test.py index 9ba44b0a8..5673395ec 100755 --- a/test/hooks.on-exit.t +++ b/test/hooks.on-exit.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,7 +43,7 @@ class TestHooksOnExit(TestCase): def test_onexit_builtin_good(self): """on-exit-good - a well-behaved, successful, on-exit hook.""" - hookname = 'on-exit-good' + hookname = "on-exit-good" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("version") @@ -58,7 +58,7 @@ class TestHooksOnExit(TestCase): def test_onexit_builtin_good_gets_changed_tasks(self): """on-exit-good - a well-behaved, successful, on-exit hook.""" - hookname = 'on-exit-good' + hookname = "on-exit-good" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -74,7 +74,7 @@ class TestHooksOnExit(TestCase): def test_onexit_builtin_bad(self): """on-exit-bad - a well-behaved, failing, on-exit hook.""" - hookname = 'on-exit-bad' + hookname = "on-exit-bad" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -90,7 +90,7 @@ class TestHooksOnExit(TestCase): def test_onexit_builtin_misbehave1(self): """on-exit-misbehave1 - Does not consume input.""" - hookname = 'on-exit-misbehave1' + hookname = "on-exit-misbehave1" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -106,7 +106,7 @@ class TestHooksOnExit(TestCase): def test_onexit_builtin_misbehave2(self): """on-exit-misbehave2 - Emits unexpected JSON.""" - hookname = 'on-exit-misbehave2' + hookname = "on-exit-misbehave2" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -120,8 +120,10 @@ class TestHooksOnExit(TestCase): logs = hook.get_logs() self.assertEqual(logs["output"]["msgs"][0], "FEEDBACK") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hooks.on-launch.t b/test/hooks.on-launch.test.py similarity index 95% rename from test/hooks.on-launch.t rename to test/hooks.on-launch.test.py index f6e2aa9b2..731486d47 100755 --- a/test/hooks.on-launch.t +++ b/test/hooks.on-launch.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,7 +43,7 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_good(self): """on-launch-good - a well-behaved, successful, on-launch hook.""" - hookname = 'on-launch-good' + hookname = "on-launch-good" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("version") @@ -58,7 +58,7 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_bad(self): """on-launch-bad - a well-behaved, failing, on-launch hook.""" - hookname = 'on-launch-bad' + hookname = "on-launch-bad" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -74,7 +74,7 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_misbehave1(self): """on-launch-misbehave1 - Hook kills itself.""" - hookname = 'on-launch-misbehave1' + hookname = "on-launch-misbehave1" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -90,7 +90,7 @@ class TestHooksOnLaunch(TestCase): def test_onlaunch_builtin_misbehave2(self): """on-launch-misbehave2 - Hook emits unexpected JSON.""" - hookname = 'on-launch-misbehave2' + hookname = "on-launch-misbehave2" self.t.hooks.add_default(hookname, log=True) # Failing hook should prevent processing. @@ -104,8 +104,10 @@ class TestHooksOnLaunch(TestCase): logs = hook.get_logs() self.assertEqual(logs["output"]["msgs"][0], "FEEDBACK") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hooks.on-modify.t b/test/hooks.on-modify.test.py similarity index 91% rename from test/hooks.on-modify.t rename to test/hooks.on-modify.test.py index 0bf7bf061..e835eeb9a 100755 --- a/test/hooks.on-modify.t +++ b/test/hooks.on-modify.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,7 +43,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_accept(self): """on-modify-accept - a well-behaved, successful, on-modify hook.""" - hookname = 'on-modify-accept' + hookname = "on-modify-accept" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -62,7 +62,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_reject(self): """on-modify-reject - a well-behaved, failing, on-modify hook.""" - hookname = 'on-modify-reject' + hookname = "on-modify-reject" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -77,7 +77,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_misbehave2(self): """on-modify-misbehave2 - does not emit JSON.""" - hookname = 'on-modify-misbehave2' + hookname = "on-modify-misbehave2" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -93,7 +93,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_misbehave3(self): """on-modify-misbehave3 - emits additional JSON.""" - hookname = 'on-modify-misbehave3' + hookname = "on-modify-misbehave3" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -109,7 +109,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_misbehave4(self): """on-modify-misbehave4 - emits different task JSON.""" - hookname = 'on-modify-misbehave4' + hookname = "on-modify-misbehave4" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -125,12 +125,12 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_misbehave5(self): """on-modify-misbehave5 - emits syntactically wrong JSON.""" - hookname = 'on-modify-misbehave5' + hookname = "on-modify-misbehave5" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") code, out, err = self.t.runError("1 modify +tag") - self.assertIn("Hook Error: JSON syntax error in: {\"}", err) + self.assertIn('Hook Error: JSON syntax error in: {"}', err) hook = self.t.hooks[hookname] hook.assertTriggeredCount(1) @@ -141,12 +141,15 @@ class TestHooksOnModify(TestCase): def test_onmodify_builtin_misbehave6(self): """on-modify-misbehave6 - emits incomplete JSON.""" - hookname = 'on-modify-misbehave6' + hookname = "on-modify-misbehave6" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") code, out, err = self.t.runError("1 modify +tag") - self.assertIn("Hook Error: JSON Object missing 'uuid' attribute from hook script: on-modify-misbehave6", err) + self.assertIn( + "Hook Error: JSON Object missing 'uuid' attribute from hook script: on-modify-misbehave6", + err, + ) hook = self.t.hooks[hookname] hook.assertTriggeredCount(1) @@ -157,7 +160,7 @@ class TestHooksOnModify(TestCase): def test_onmodify_revert_changes(self): """on-modify-revert - revert all user modifications.""" - hookname = 'on-modify-revert' + hookname = "on-modify-revert" self.t.hooks.add_default(hookname, log=True) code, out, err = self.t("add foo") @@ -173,8 +176,10 @@ class TestHooksOnModify(TestCase): hook.assertTriggeredCount(1) hook.assertExitcode(0) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/hyphenate.t b/test/hyphenate.test.py similarity index 90% rename from test/hyphenate.t rename to test/hyphenate.test.py index 424daa18d..c2c7466a8 100755 --- a/test/hyphenate.t +++ b/test/hyphenate.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,8 +40,8 @@ class TestHyphenation(TestCase): """Executed before each test in the class""" self.t = Task() self.t.config("defaultwidth", "20") - self.t.config("detection", "0") - self.t.config("verbose", "nothing") + self.t.config("detection", "0") + self.t.config("verbose", "nothing") def test_hyphenation_on_space(self): """Split on space instead of hyphenating""" @@ -55,6 +55,7 @@ class TestHyphenation(TestCase): code, out, err = self.t("ls") self.assertIn(" 1 AAAAAAAAAABBBBBB-\n", out) + class TestBug804(TestCase): def setUp(self): """Executed before each test in the class""" @@ -62,10 +63,10 @@ class TestBug804(TestCase): def test_hyphenation(self): """Verify hyphenation is controllable""" - self.t.config("print.empty.columns", "1") - self.t.config("report.unittest.labels", "ID,Project,Pri,Description") + self.t.config("print.empty.columns", "1") + self.t.config("report.unittest.labels", "ID,Project,Pri,Description") self.t.config("report.unittest.columns", "id,project,priority,description") - self.t.config("report.unittest.filter", "status:pending") + self.t.config("report.unittest.filter", "status:pending") # Setup: Add a tasks, annotate with long word. self.t("add one") @@ -84,6 +85,7 @@ class TestBug804(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/ids.t b/test/ids.test.py similarity index 93% rename from test/ids.t rename to test/ids.test.py index 4531f4d51..4dc30935e 100755 --- a/test/ids.t +++ b/test/ids.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import tempfile import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,9 +43,9 @@ class TestIDs(TestCase): self.t = Task() self.t("add one +A +B") - self.t("add two +A" ) + self.t("add two +A") self.t("add three +A +B") - self.t("add four" ) + self.t("add four") self.t("add five +A +B") def test_ids_count_A(self): @@ -87,7 +87,8 @@ class TestIDs(TestCase): """_zshuuids +A""" code, out, err = self.t("_zshuuids +A") self.assertRegex( - out, "{0}:one\n{0}:two\n{0}:three\n{0}:five".format(UUID_REGEXP)) + out, "{0}:one\n{0}:two\n{0}:three\n{0}:five".format(UUID_REGEXP) + ) def test_ids_ranges(self): """Verify consecutive IDs are compressed into a range""" @@ -108,8 +109,8 @@ class TestIDMisParse(TestCase): def test_parse_numbers_as_ids_not_patterns(self): """Verify that numbers are parsed as IDs""" - self.t("add 2 two") # ID 1 - self.t("add 1 one") # ID 2 + self.t("add 2 two") # ID 1 + self.t("add 1 one") # ID 2 self.t("add 3 three") # ID 3 code, out, err = self.t("2 ls rc.verbose:nothing") @@ -125,11 +126,13 @@ class TestIDRangeParsing(TestCase): def generate_tasks(self, n): """Generates n tasks for testing purposes""" - with tempfile.NamedTemporaryFile(mode='w') as f: - f.write('\n'.join([f'{{"description": "test task {i+1}"}}' for i in range(n)])) + with tempfile.NamedTemporaryFile(mode="w") as f: + f.write( + "\n".join([f'{{"description": "test task {i+1}"}}' for i in range(n)]) + ) f.flush() # use a long timeout here, because import is quite slow - code, out, err = self.t(f'import {f.name}', timeout=100) + code, out, err = self.t(f"import {f.name}", timeout=100) def test_single_digit_range(self): """Test that parsing single digit ID range works""" @@ -168,6 +171,7 @@ class TestIDRangeParsing(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/import-v2.test.py b/test/import-v2.test.py new file mode 100755 index 000000000..063feedae --- /dev/null +++ b/test/import-v2.test.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import unittest +import json + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase +from basetest.utils import mkstemp + + +class TestImport(TestCase): + def setUp(self): + self.t = Task() + self.t.config("dateformat", "m/d/Y") + + # Multiple tasks. + self.pending = """\ +[description:"bing" due:"1734480000" entry:"1734397061" modified:"1734397061" status:"pending" uuid:"ad7f7585-bff3-4b57-a116-abfc9f71ee4a"] +[description:"baz" entry:"1734397063" modified:"1734397063" status:"pending" uuid:"591ccfee-dd8d-44e9-908a-40618257cf54"]\ +""" + self.completed = """\ +[description:"foo" end:"1734397073" entry:"1734397054" modified:"1734397074" status:"deleted" uuid:"6849568f-55d7-4152-8db0-00356e39f0bb"] +[description:"bar" end:"1734397065" entry:"1734397056" modified:"1734397065" status:"completed" uuid:"51921813-7abb-412d-8ada-7c1417d01209"]\ +""" + + def test_import_v2(self): + with open(os.path.join(self.t.datadir, "pending.data"), "w") as f: + f.write(self.pending) + with open(os.path.join(self.t.datadir, "completed.data"), "w") as f: + f.write(self.completed) + code, out, err = self.t("import-v2") + self.assertIn("Imported 4 tasks", err) + + code, out, err = self.t("list") + self.assertIn("bing", out) + self.assertIn("baz", out) + self.assertNotIn("foo", out) + self.assertNotIn("bar", out) + + code, out, err = self.t("completed") + self.assertNotIn("bing", out) + self.assertNotIn("baz", out) + self.assertNotIn("foo", out) # deleted, not in the completed report + self.assertIn("bar", out) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/import.t b/test/import.test.py similarity index 92% rename from test/import.t rename to test/import.test.py index 83b17b2ec..1c709c23d 100755 --- a/test/import.t +++ b/test/import.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import json + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -180,7 +180,7 @@ class TestImport(TestCase): def test_import_newlines_whitespace(self): """JSON array with whitespace before and after names and values""" _data = """[ -{ "uuid":"a0000000-a000-a000-a000-a00000000000" , "description" : "zero" ,"project":"A", "status":"pending","entry":"1234567889" } , +{ "uuid":"a0000000-a000-a000-a000-a00000000000" , "description" : "zero" ,"project":"A", "status":"pending","entry":"1234567889" } , { "uuid":"a1111111-a111-a111-a111-a11111111111","description":"one","project":"B","status":"pending","entry":"1234567889"}, {"uuid":"a2222222-a222-a222-a222-a22222222222","description":"two","status":"completed","entry":"1234524689","end":"1234524690" } ]""" code, out, err = self.t("import", input=_data) @@ -211,19 +211,35 @@ class TestImport(TestCase): self.t("import", input=_data) _t = self.t.export("a0000000-a000-a000-a000-a00000000000")[0] - for _uuid in ["a1111111-a111-a111-a111-a11111111111","a2222222-a222-a222-a222-a22222222222"]: + for _uuid in [ + "a1111111-a111-a111-a111-a11111111111", + "a2222222-a222-a222-a222-a22222222222", + ]: self.assertTrue((_t["depends"][0] == _uuid) or (_t["depends"][1] == _uuid)) def test_import_same_task_twice(self): """Test import same task twice""" - _data = """{"uuid":"a1111111-a222-a333-a444-a55555555555","description":"data4"}""" + _data = ( + """{"uuid":"a1111111-a222-a333-a444-a55555555555","description":"data4"}""" + ) self.t("import", input=_data) code, out1, err = self.t("export") - self.t.faketime('+1s') + self.t.faketime("+1s") self.t("import", input=_data) code, out2, err = self.t("export") self.assertEqual(out1, out2) + def test_import_duplicate_uuid_in_input(self): + """Test import warns if input contains the same UUID twice.""" + _data = """[ +{"uuid":"a0000000-a000-a000-a000-a00000000000","description":"first description"}, +{"uuid":"a0000000-a000-a000-a000-a00000000000","description":"second description"} +]""" + _, _, err = self.t("import", input=_data) + self.assertIn( + "Input contains UUID 'a0000000-a000-a000-a000-a00000000000' 2 times", err + ) + class TestImportExportRoundtrip(TestCase): def setUp(self): @@ -231,8 +247,8 @@ class TestImportExportRoundtrip(TestCase): self.t2 = Task() for client in (self.t1, self.t2): - client.config("dateformat", "m/d/Y") - client.config("verbose", "0") + client.config("dateformat", "m/d/Y") + client.config("verbose", "0") client.config("defaultwidth", "100") def _validate_data(self, client): @@ -268,10 +284,10 @@ class TestImportValidate(TestCase): self.t = Task() def test_import_empty_json(self): - """Verify empty JSON is caught""" - j = '{}' + """Verify empty JSON is ignored""" + j = "{}" code, out, err = self.t.runError("import", input=j) - self.assertIn("A task must have a description.", err) + self.assertIn("Cannot import an empty task.", err) def test_import_invalid_uuid(self): """Verify invalid UUID is caught""" @@ -335,6 +351,7 @@ class TestBug1441(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/info.t b/test/info.test.py similarity index 70% rename from test/info.t rename to test/info.test.py index 23181db32..79ffe80fa 100755 --- a/test/info.t +++ b/test/info.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -60,25 +60,27 @@ class TestInfoCommand(TestCase): self.t.config("urgency.user.keyword.foo.coefficient", "1.0") self.t.config("urgency.uda.u_one.coefficient", "1.0") - self.t("add foo project:P +tag priority:H start:now due:eom wait:eom scheduled:eom recur:P1M until:eoy u_one:now u_two:1day") + self.t( + "add foo project:P +tag priority:H start:now due:eom wait:eom scheduled:eom recur:P1M until:eoy u_one:now u_two:1day" + ) self.t("1 annotate bar", input="n\n") code, out, err = self.t("1 info") - self.assertRegex(out, "ID\s+1") - self.assertRegex(out, "Description\s+foo") - self.assertRegex(out, "\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\s+bar") - self.assertRegex(out, "Status\s+Recurring") - self.assertRegex(out, "Project\s+P") - self.assertRegex(out, "Recurrence\s+P1M") - self.assertRegex(out, "Entered\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Waiting until\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Scheduled\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Start\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Due\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Until\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Last modified\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"ID\s+1") + self.assertRegex(out, r"Description\s+foo") + self.assertRegex(out, r"\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\s+bar") + self.assertRegex(out, r"Status\s+Recurring") + self.assertRegex(out, r"Project\s+P") + self.assertRegex(out, r"Recurrence\s+P1M") + self.assertRegex(out, r"Entered\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Waiting until\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Scheduled\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Start\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Due\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Until\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") + self.assertRegex(out, r"Last modified\s+\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}") - self.assertRegex(out, "Tags\s+tag") + self.assertRegex(out, r"Tags\s+tag") self.assertIn("ACTIVE", out) self.assertIn("ANNOTATED", out) self.assertIn("MONTH", out) @@ -89,10 +91,16 @@ class TestInfoCommand(TestCase): self.assertIn("YEAR", out) self.assertIn("UDA", out) - self.assertRegex(out, "UUID\s+[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}") - self.assertRegex(out, "Urgency\s+\d+(\.\d+)?") - self.assertRegex(out, "Priority\s+H") + self.assertRegex( + out, + r"UUID\s+[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", + ) + self.assertRegex(out, r"Urgency\s+\d+(\.\d+)?") + self.assertRegex(out, r"Priority\s+H") + self.assertRegex(out, r"Annotation of 'bar' added\.") + self.assertRegex(out, r"Tag 'tag' added\.") + self.assertRegex(out, r"tatus set to 'recurring'\.") self.assertIn("project", out) self.assertIn("active", out) self.assertIn("annotations", out) @@ -106,6 +114,7 @@ class TestInfoCommand(TestCase): self.assertRegex(out, r"U_ONE\s+\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}") self.assertRegex(out, r"U_TWO\s+P1D") + class TestBug425(TestCase): def setUp(self): self.t = Task() @@ -116,11 +125,12 @@ class TestBug425(TestCase): self.t("1 modify Bar in Bar") code, out, err = self.t("1 ls") - self.assertRegex(out, "1\s+Bar in Bar") + self.assertRegex(out, r"1\s+Bar in Bar") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/lexer.t.cpp b/test/lexer.t.cpp deleted file mode 100644 index 787b9c16a..000000000 --- a/test/lexer.t.cpp +++ /dev/null @@ -1,600 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ -#ifdef PRODUCT_TASKWARRIOR - UnitTest t (1253); -#else - UnitTest t (1235); -#endif - - // Use same Datetime/Duraiton configuration as Context∴:staticInitialization. - Datetime::isoEnabled = true; - Datetime::standaloneDateEnabled = false; - Datetime::standaloneTimeEnabled = false; - Duration::standaloneSecondsEnabled = false; - - std::vector > tokens; - std::string token; - Lexer::Type type; - - // Feed in some attributes and types, so that the Lexer knows what a DOM - // reference is. - Lexer::attributes["due"] = "date"; - Lexer::attributes["tags"] = "string"; - Lexer::attributes["description"] = "string"; - - // static bool Lexer::isBoundary (int, int); - t.ok (Lexer::isBoundary (' ', 'a'), "' ' --> 'a' = isBoundary"); - t.ok (Lexer::isBoundary ('a', ' '), "'a' --> ' ' = isBoundary"); - t.ok (Lexer::isBoundary (' ', '+'), "' ' --> '+' = isBoundary"); - t.ok (Lexer::isBoundary (' ', ','), "' ' --> ',' = isBoundary"); - t.notok (Lexer::isBoundary ('3', '4'), "'3' --> '4' = isBoundary"); - t.ok (Lexer::isBoundary ('(', '('), "'(' --> '(' = isBoundary"); - t.notok (Lexer::isBoundary ('r', 'd'), "'r' --> 'd' = isBoundary"); - - // static bool Lexer::wasQuoted (const std::string&); - t.notok (Lexer::wasQuoted (""), "'' --> !wasQuoted"); - t.notok (Lexer::wasQuoted ("foo"), "'foo' --> !wasQuoted"); - t.ok (Lexer::wasQuoted ("a b"), "'a b' --> wasQuoted"); - t.ok (Lexer::wasQuoted ("(a)"), "'(a)' --> wasQuoted"); - - // static bool Lexer::dequote (std::string&, const std::string& quotes = "'\""); - token = "foo"; - Lexer::dequote (token); - t.is (token, "foo", "dequote foo --> foo"); - - token = "'foo'"; - Lexer::dequote (token); - t.is (token, "foo", "dequote 'foo' --> foo"); - - token = "'o\\'clock'"; - Lexer::dequote (token); - t.is (token, "o\\'clock", "dequote 'o\\'clock' --> o\\'clock"); - - token = "abba"; - Lexer::dequote (token, "a"); - t.is (token, "bb", "dequote 'abba' (a) --> bb"); - - // Should result in no tokens. - Lexer l0 (""); - t.notok (l0.token (token, type), "'' --> no tokens"); - - // Should result in no tokens. - Lexer l1 (" \t "); - t.notok (l1.token (token, type), "' \\t ' --> no tokens"); - - // \u20ac = Euro symbol. - Lexer l2 (R"( one 'two \'three\''+456-(1.3*2 - 0x12) 1.2e-3.4 foo.bar and '\u20ac')"); - - tokens.clear (); - while (l2.token (token, type)) - { - std::cout << "# «" << token << "» " << Lexer::typeName (type) << "\n"; - tokens.emplace_back (token, type); - } - - t.is (tokens[0].first, "one", "tokens[0] = 'one'"); // 30 - t.is (Lexer::typeName (tokens[0].second), "identifier", "tokens[0] = identifier"); - t.is (tokens[1].first, "'two 'three''", "tokens[1] = 'two 'three''"); - t.is (Lexer::typeName (tokens[1].second), "string", "tokens[1] = string"); - t.is (tokens[2].first, "+", "tokens[2] = '+'"); - t.is (Lexer::typeName (tokens[2].second), "op", "tokens[2] = op"); - t.is (tokens[3].first, "456", "tokens[3] = '456'"); - t.is (Lexer::typeName (tokens[3].second), "number", "tokens[3] = number"); - t.is (tokens[4].first, "-", "tokens[4] = '-'"); - t.is (Lexer::typeName (tokens[4].second), "op", "tokens[4] = op"); - t.is (tokens[5].first, "(", "tokens[5] = '('"); // 40 - t.is (Lexer::typeName (tokens[5].second), "op", "tokens[5] = op"); - t.is (tokens[6].first, "1.3", "tokens[6] = '1.3'"); - t.is (Lexer::typeName (tokens[6].second), "number", "tokens[6] = number"); - t.is (tokens[7].first, "*", "tokens[7] = '*'"); - t.is (Lexer::typeName (tokens[7].second), "op", "tokens[7] = op"); - t.is (tokens[8].first, "2", "tokens[8] = '2'"); - t.is (Lexer::typeName (tokens[8].second), "number", "tokens[8] = number"); - t.is (tokens[9].first, "-", "tokens[9] = '-'"); - t.is (Lexer::typeName (tokens[9].second), "op", "tokens[9] = op"); - t.is (tokens[10].first, "0x12", "tokens[10] = '0x12'"); // 50 - t.is (Lexer::typeName (tokens[10].second), "hex", "tokens[10] = hex"); - t.is (tokens[11].first, ")", "tokens[11] = ')'"); - t.is (Lexer::typeName (tokens[11].second), "op", "tokens[11] = op"); - t.is (tokens[12].first, "1.2e-3.4", "tokens[12] = '1.2e-3.4'"); - t.is (Lexer::typeName (tokens[12].second), "number", "tokens[12] = number"); - t.is (tokens[13].first, "foo.bar", "tokens[13] = 'foo.bar'"); - t.is (Lexer::typeName (tokens[13].second), "identifier", "tokens[13] = identifier"); - t.is (tokens[14].first, "and", "tokens[14] = 'and'"); // 60 - t.is (Lexer::typeName (tokens[14].second), "op", "tokens[14] = op"); - t.is (tokens[15].first, "'€'", "tokens[15] = \\u20ac --> ''€''"); - t.is (Lexer::typeName (tokens[15].second), "string", "tokens[15] = string"); - - // Test for numbers that are no longer ISO-8601 dates. - Lexer l3 ("1 12 123 1234 12345 123456 1234567"); - tokens.clear (); - while (l3.token (token, type)) - { - std::cout << "# «" << token << "» " << Lexer::typeName (type) << "\n"; - tokens.emplace_back (token, type); - } - - t.is ((int)tokens.size (), 7, "7 tokens"); - t.is (tokens[0].first, "1", "tokens[0] == '1'"); - t.is ((int) tokens[0].second, (int) Lexer::Type::number, "tokens[0] == Type::number"); - t.is (tokens[1].first, "12", "tokens[1] == '12'"); - t.is ((int) tokens[1].second, (int) Lexer::Type::number, "tokens[1] == Type::number"); - t.is (tokens[2].first, "123", "tokens[2] == '123'"); - t.is ((int) tokens[2].second, (int) Lexer::Type::number, "tokens[2] == Type::number"); // 70 - t.is (tokens[3].first, "1234", "tokens[3] == '1234'"); - t.is ((int) tokens[3].second, (int) Lexer::Type::number, "tokens[3] == Type::number"); - t.is (tokens[4].first, "12345", "tokens[4] == '12345'"); - t.is ((int) tokens[4].second, (int) Lexer::Type::number, "tokens[4] == Type::number"); - t.is (tokens[5].first, "123456", "tokens[5] == '123456'"); - t.is ((int) tokens[5].second, (int) Lexer::Type::number, "tokens[5] == Type::number"); - t.is (tokens[6].first, "1234567", "tokens[6] == '1234567'"); - t.is ((int) tokens[6].second, (int) Lexer::Type::number, "tokens[6] == Type::number"); - - // void split (std::vector&, const std::string&); - std::string unsplit = " ( A or B ) "; - std::vector items; - items = Lexer::split (unsplit); - t.is (items.size (), (size_t) 5, "split ' ( A or B ) '"); - t.is (items[0], "(", "split ' ( A or B ) ' -> [0] '('"); - t.is (items[1], "A", "split ' ( A or B ) ' -> [1] 'A'"); - t.is (items[2], "or", "split ' ( A or B ) ' -> [2] 'or'"); - t.is (items[3], "B", "split ' ( A or B ) ' -> [3] 'B'"); - t.is (items[4], ")", "split ' ( A or B ) ' -> [4] ')'"); - - // Test simple mode with contrived tokens that ordinarily split. - unsplit = " +-* a+b 12.3e4 'c d'"; - items = Lexer::split (unsplit); - t.is (items.size (), (size_t) 8, "split ' +-* a+b 12.3e4 'c d''"); - t.is (items[0], "+", "split ' +-* a+b 12.3e4 'c d'' -> [0] '+'"); - t.is (items[1], "-", "split ' +-* a+b 12.3e4 'c d'' -> [1] '-'"); - t.is (items[2], "*", "split ' +-* a+b 12.3e4 'c d'' -> [2] '*'"); - t.is (items[3], "a", "split ' +-* a+b 12.3e4 'c d'' -> [3] 'a'"); - t.is (items[4], "+", "split ' +-* a+b 12.3e4 'c d'' -> [4] '+'"); - t.is (items[5], "b", "split ' +-* a+b 12.3e4 'c d'' -> [5] 'b'"); - t.is (items[6], "12.3e4", "split ' +-* a+b 12.3e4 'c d'' -> [6] '12.3e4'"); - t.is (items[7], "'c d'", "split ' +-* a+b 12.3e4 'c d'' -> [7] ''c d''"); - - // static bool decomposePair (const std::string&, std::string&, std::string&, std::string&, std::string&); - // 2 * 4 * 2 * 5 = 80 tests. - std::string outName, outMod, outValue, outSep; - for (auto& name : {"name"}) - { - for (auto& mod : {"", "mod"}) - { - for (auto& sep : {":", "=", "::", ":="}) - { - for (auto& value : {"", "value", "a:b", "a::b", "a=b", "a:=b"}) - { - std::string input = std::string ("name") + (strlen (mod) ? "." : "") + mod + sep + value; - t.ok (Lexer::decomposePair (input, outName, outMod, outSep, outValue), "decomposePair '" + input + "' --> true"); - t.is (name, outName, " '" + input + "' --> name '" + name + "'"); - t.is (mod, outMod, " '" + input + "' --> mod '" + mod + "'"); - t.is (value, outValue, " '" + input + "' --> value '" + value + "'"); - t.is (sep, outSep, " '" + input + "' --> sep '" + sep + "'"); - } - } - } - } - - // static bool readWord (const std::string&, const std::string&, std::string::size_type&, std::string&); - std::string::size_type cursor = 0; - std::string word; - t.ok (Lexer::readWord ("'one two'", "'\"", cursor, word), "readWord ''one two'' --> true"); - t.is (word, "'one two'", " word '" + word + "'"); - t.is ((int)cursor, 9, " cursor"); - - // Unterminated quoted string is invalid. - cursor = 0; - t.notok (Lexer::readWord ("'one", "'\"", cursor, word), "readWord ''one' --> false"); - - // static bool readWord (const std::string&, std::string::size_type&, std::string&); - cursor = 0; - t.ok (Lexer::readWord ("input", cursor, word), "readWord 'input' --> true"); - t.is (word, "input", " word '" + word + "'"); - t.is ((int)cursor, 5, " cursor"); - - cursor = 0; - t.ok (Lexer::readWord ("one\\ two", cursor, word), "readWord 'one\\ two' --> true"); - t.is (word, "one two", " word '" + word + "'"); - t.is ((int)cursor, 8, " cursor"); - - cursor = 0; - t.ok (Lexer::readWord ("\\u20A43", cursor, word), "readWord '\\u20A43' --> true"); - t.is (word, "₤3", " word '" + word + "'"); - t.is ((int)cursor, 7, " cursor"); - - cursor = 0; - t.ok (Lexer::readWord ("U+20AC4", cursor, word), "readWord '\\u20AC4' --> true"); - t.is (word, "€4", " word '" + word + "'"); - t.is ((int)cursor, 7, " cursor"); - - std::string text = "one 'two' three\\ four"; - cursor = 0; - t.ok (Lexer::readWord (text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); - t.is (word, "one", " word '" + word + "'"); - cursor++; - t.ok (Lexer::readWord (text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); - t.is (word, "'two'", " word '" + word + "'"); - cursor++; - t.ok (Lexer::readWord (text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); - t.is (word, "three four", " word '" + word + "'"); - - text = "one "; - cursor = 0; - t.ok (Lexer::readWord (text, cursor, word), "readWord \"one \" --> true"); - t.is (word, "one", " word '" + word + "'"); - - // bool isLiteral (const std::string&, bool, bool); - Lexer l4 ("one.two"); - t.notok (l4.isLiteral("zero", false, false), "isLiteral 'one.two' --> false"); - t.ok (l4.isLiteral("one", false, false), "isLiteral 'one.two' --> 'one'"); - t.ok (l4.isLiteral(".", false, false), "isLiteral 'one.two' --> '.'"); - t.ok (l4.isLiteral("two", false, true), "isLiteral 'one.two' --> 'two'"); - - Lexer l5 ("wonder"); - t.notok (l5.isLiteral ("wonderful", false, false), "isLiteral 'wonderful' != 'wonder' without abbreviation"); - t.ok (l5.isLiteral ("wonderful", true, false), "isLiteral 'wonderful' == 'wonder' with abbreviation"); - - // bool isOneOf (const std::string&, bool, bool); - Lexer l6 ("Grumpy."); - std::vector dwarves = {"Sneezy", "Doc", "Bashful", "Grumpy", "Happy", "Sleepy", "Dopey"}; - t.notok (l6.isOneOf (dwarves, false, true), "isOneof ('Grumpy', true) --> false"); - t.ok (l6.isOneOf (dwarves, false, false), "isOneOf ('Grumpy', false) --> true"); - - // static std::string::size_type commonLength (const std::string&, const std::string&); - t.is ((int)Lexer::commonLength ("", ""), 0, "commonLength '' : '' --> 0"); - t.is ((int)Lexer::commonLength ("a", "a"), 1, "commonLength 'a' : 'a' --> 1"); - t.is ((int)Lexer::commonLength ("abcde", "abcde"), 5, "commonLength 'abcde' : 'abcde' --> 5"); - t.is ((int)Lexer::commonLength ("abc", ""), 0, "commonLength 'abc' : '' --> 0"); - t.is ((int)Lexer::commonLength ("abc", "def"), 0, "commonLength 'abc' : 'def' --> 0"); - t.is ((int)Lexer::commonLength ("foobar", "foo"), 3, "commonLength 'foobar' : 'foo' --> 3"); - t.is ((int)Lexer::commonLength ("foo", "foobar"), 3, "commonLength 'foo' : 'foobar' --> 3"); - - // static std::string::size_type commonLength (const std::string&, std::string::size_type, const std::string&, std::string::size_type); - t.is ((int)Lexer::commonLength ("wonder", 0, "prowonderbread", 3), 6, "'wonder'+0 : 'prowonderbread'+3 --> 6"); - - // Test all Lexer types. - #define NO {"",Lexer::Type::word} - struct - { - const char* input; - struct - { - const char* token; - Lexer::Type type; - bool expfail_token = false; - bool expfail_type = false; - } results[5]; - } lexerTests[] = - { - // Pattern - { "/foo/", { { "/foo/", Lexer::Type::pattern }, NO, NO, NO, NO }, }, - { "/a\\/b/", { { "/a\\/b/", Lexer::Type::pattern }, NO, NO, NO, NO }, }, - { "/'/", { { "/'/", Lexer::Type::pattern }, NO, NO, NO, NO }, }, - - // Substitution - { "/from/to/g", { { "/from/to/g", Lexer::Type::substitution }, NO, NO, NO, NO }, }, - { "/from/to/", { { "/from/to/", Lexer::Type::substitution }, NO, NO, NO, NO }, }, - - // Tag - { "+tag", { { "+tag", Lexer::Type::tag }, NO, NO, NO, NO }, }, - { "-tag", { { "-tag", Lexer::Type::tag }, NO, NO, NO, NO }, }, - { "+@tag", { { "+@tag", Lexer::Type::tag }, NO, NO, NO, NO }, }, - - // Path - { "/long/path/to/file.txt", { { "/long/path/to/file.txt", Lexer::Type::path }, NO, NO, NO, NO }, }, - - // Word - { "1.foo.bar", { { "1.foo.bar", Lexer::Type::word }, NO, NO, NO, NO }, }, - - // Identifier - { "foo", { { "foo", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "Çirçös", { { "Çirçös", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "☺", { { "☺", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "name", { { "name", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "f1", { { "f1", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "foo.bar", { { "foo.bar", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - { "a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", { { "a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - - // Word that starts wih 'or', which is an operator, but should be ignored. - { "ordinary", { { "ordinary", Lexer::Type::identifier }, NO, NO, NO, NO }, }, - - // DOM - { "due", { { "due", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.tags", { { "123.tags", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.tags.PENDING", { { "123.tags.PENDING", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.description", { { "123.description", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.annotations.1.description", { { "123.annotations.1.description", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.annotations.1.entry", { { "123.annotations.1.entry", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "123.annotations.1.entry.year", { { "123.annotations.1.entry.year", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "a360fc44-315c-4366-b70c-ea7e7520b749.due", { { "a360fc44-315c-4366-b70c-ea7e7520b749.due", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "12345678-1234-1234-1234-123456789012.due", { { "12345678-1234-1234-1234-123456789012.due", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "system.os", { { "system.os", Lexer::Type::dom }, NO, NO, NO, NO }, }, - { "rc.foo", { { "rc.foo", Lexer::Type::dom }, NO, NO, NO, NO }, }, - - // URL - { "http://example.com", { { "http://example.com", Lexer::Type::url }, NO, NO, NO, NO }, }, - { "https://foo.example.com", { { "https://foo.example.com", Lexer::Type::url }, NO, NO, NO, NO }, }, - - // String - { "'one two'", { { "'one two'", Lexer::Type::string }, NO, NO, NO, NO }, }, - { "\"three\"", { { "\"three\"", Lexer::Type::string }, NO, NO, NO, NO }, }, - { "'\\''", { { "'''", Lexer::Type::string }, NO, NO, NO, NO }, }, - {R"("\"")", { {R"(""")", Lexer::Type::string }, NO, NO, NO, NO }, }, - { "\"\tfoo\t\"", { { "\"\tfoo\t\"", Lexer::Type::string }, NO, NO, NO, NO }, }, - {R"("\u20A43")", { { "\"₤3\"", Lexer::Type::string }, NO, NO, NO, NO }, }, - { "\"U+20AC4\"", { { "\"€4\"", Lexer::Type::string }, NO, NO, NO, NO }, }, - - // Number - { "1", { { "1", Lexer::Type::number }, NO, NO, NO, NO }, }, - { "3.14", { { "3.14", Lexer::Type::number }, NO, NO, NO, NO }, }, - { "6.02217e23", { { "6.02217e23", Lexer::Type::number }, NO, NO, NO, NO }, }, - { "1.2e-3.4", { { "1.2e-3.4", Lexer::Type::number }, NO, NO, NO, NO }, }, - { "0x2f", { { "0x2f", Lexer::Type::hex }, NO, NO, NO, NO }, }, - - // Set (1,2,4-7,9) - { "1,2", { { "1,2", Lexer::Type::set }, NO, NO, NO, NO }, }, - { "1-2", { { "1-2", Lexer::Type::set }, NO, NO, NO, NO }, }, - { "1-2,4", { { "1-2,4", Lexer::Type::set }, NO, NO, NO, NO }, }, - { "1-2,4,6-8", { { "1-2,4,6-8", Lexer::Type::set }, NO, NO, NO, NO }, }, - { "1-2,4,6-8,10-12", { { "1-2,4,6-8,10-12", Lexer::Type::set }, NO, NO, NO, NO }, }, - - // Pair - { "name:value", { { "name:value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name=value", { { "name=value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name:=value", { { "name:=value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name.mod:value", { { "name.mod:value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name.mod=value", { { "name.mod=value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name:", { { "name:", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name=", { { "name=", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name.mod:", { { "name.mod:", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name.mod=", { { "name.mod=", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "pro:'P 1'", { { "pro:'P 1'", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "rc:x", { { "rc:x", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "rc.name:value", { { "rc.name:value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "rc.name=value", { { "rc.name=value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "rc.name:=value", { { "rc.name:=value", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "due:='eow - 2d'", { { "due:='eow - 2d'", Lexer::Type::pair }, NO, NO, NO, NO }, }, - { "name:'foo\nbar'", { { "name:'foo\nbar'", Lexer::Type::pair }, NO, NO, NO, NO }, }, - - // Operator - complete set - { "^", { { "^", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "!", { { "!", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "_neg_", { { "_neg_", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "_pos_", { { "_pos_", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "_hastag_", { { "_hastag_", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "_notag_", { { "_notag_", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "*", { { "*", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "/", { { "/", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "%", { { "%", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "+", { { "+", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "-", { { "-", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "<=", { { "<=", Lexer::Type::op }, NO, NO, NO, NO }, }, - { ">=", { { ">=", Lexer::Type::op }, NO, NO, NO, NO }, }, - { ">", { { ">", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "<", { { "<", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "=", { { "=", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "==", { { "==", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "!=", { { "!=", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "!==", { { "!==", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "~", { { "~", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "!~", { { "!~", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "and", { { "and", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "or", { { "or", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "xor", { { "xor", Lexer::Type::op }, NO, NO, NO, NO }, }, - { "(", { { "(", Lexer::Type::op }, NO, NO, NO, NO }, }, - { ")", { { ")", Lexer::Type::op }, NO, NO, NO, NO }, }, - - // UUID - { "ffffffff-ffff-ffff-ffff-ffffffffffff", { { "ffffffff-ffff-ffff-ffff-ffffffffffff", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "0000000d-0000-0000-0000-000000000000", { { "0000000d-0000-0000-0000-000000000000", Lexer::Type::uuid, true, true }, NO, NO, NO, NO }, }, - { "00000000-0000-0000-0000-0000000", { { "00000000-0000-0000-0000-0000000", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "00000000-0000-0000-0000", { { "00000000-0000-0000-0000", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "00000000-0000-0000", { { "00000000-0000-0000", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "00000000-0000", { { "00000000-0000", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "00000000", { { "00000000", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44-315c-4366-b70c-ea7e7520b749", { { "a360fc44-315c-4366-b70c-ea7e7520b749", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44-315c-4366-b70c-ea7e752", { { "a360fc44-315c-4366-b70c-ea7e752", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44-315c-4366-b70c", { { "a360fc44-315c-4366-b70c", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44-315c-4366", { { "a360fc44-315c-4366", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44-315c", { { "a360fc44-315c", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - { "a360fc44", { { "a360fc44", Lexer::Type::uuid }, NO, NO, NO, NO }, }, - - // Date - { "2015-W01", { { "2015-W01", Lexer::Type::date }, NO, NO, NO, NO }, }, - { "2015-02-17", { { "2015-02-17", Lexer::Type::date }, NO, NO, NO, NO }, }, - { "2013-11-29T22:58:00Z", { { "2013-11-29T22:58:00Z", Lexer::Type::date }, NO, NO, NO, NO }, }, - { "20131129T225800Z", { { "20131129T225800Z", Lexer::Type::date }, NO, NO, NO, NO }, }, -#ifdef PRODUCT_TASKWARRIOR - { "9th", { { "9th", Lexer::Type::date }, NO, NO, NO, NO }, }, - { "10th", { { "10th", Lexer::Type::date }, NO, NO, NO, NO }, }, - { "today", { { "today", Lexer::Type::date }, NO, NO, NO, NO }, }, -#endif - - // Duration - { "year", { { "year", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "4weeks", { { "4weeks", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "PT23H", { { "PT23H", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "1second", { { "1second", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "1s", { { "1s", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "1minute", { { "1minute", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "2hour", { { "2hour", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "3 days", { { "3 days", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "4w", { { "4w", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "5mo", { { "5mo", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "6 years", { { "6 years", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "P1Y", { { "P1Y", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "PT1H", { { "PT1H", Lexer::Type::duration }, NO, NO, NO, NO }, }, - { "P1Y1M1DT1H1M1S", { { "P1Y1M1DT1H1M1S", Lexer::Type::duration }, NO, NO, NO, NO }, }, - - // Misc - { "--", { { "--", Lexer::Type::separator }, NO, NO, NO, NO }, }, - - // Expression - // due:eom-2w - // due < eom + 1w + 1d - // ( /pattern/ or 8ad2e3db-914d-4832-b0e6-72fa04f6e331,3b6218f9-726a-44fc-aa63-889ff52be442 ) - { "(1+2)", { { "(", Lexer::Type::op }, - { "1", Lexer::Type::number }, - { "+", Lexer::Type::op }, - { "2", Lexer::Type::number }, - { ")", Lexer::Type::op }, }, }, - { "description~pattern", { { "description", Lexer::Type::dom }, - { "~", Lexer::Type::op }, - { "pattern", Lexer::Type::identifier }, NO, NO }, }, - { "(+tag)", { { "(", Lexer::Type::op }, - { "+tag", Lexer::Type::tag }, - { ")", Lexer::Type::op }, NO, NO }, }, - { "(name:value)", { { "(", Lexer::Type::op }, - { "name:value", Lexer::Type::pair }, - { ")", Lexer::Type::op }, NO, NO }, }, - }; - - for (const auto& lexerTest : lexerTests) - { - // The isolated test puts the input string directly into the Lexer. - Lexer isolated (lexerTest.input); - - for (const auto& result : lexerTest.results) - { - if (result.token[0]) - { - // Isolated: "" - t.ok (isolated.token (token, type), "Isolated Lexer::token(...) --> true"); - t.is (token, result.token, " token --> " + token, result.expfail_token); - t.is ((int)type, (int)result.type, " type --> Lexer::Type::" + Lexer::typeToString (type), result.expfail_type); - } - } - - // The embedded test surrounds the input string with a space. - Lexer embedded (std::string (" ") + lexerTest.input + " "); - - for (const auto& result : lexerTest.results) - { - if (result.token[0]) - { - // Embedded: "" - t.ok (embedded.token (token, type), "Embedded Lexer::token(...) --> true"); - t.is (token, result.token, " token --> " + token, result.expfail_token); - t.is ((int)type, (int)result.type, " type --> Lexer::Type::" + Lexer::typeToString (type), result.expfail_type); - } - } - } - - t.is (Lexer::typeName (Lexer::Type::uuid), "uuid", "Lexer::typeName (Lexer::Type::uuid)"); - t.is (Lexer::typeName (Lexer::Type::number), "number", "Lexer::typeName (Lexer::Type::number)"); - t.is (Lexer::typeName (Lexer::Type::hex), "hex", "Lexer::typeName (Lexer::Type::hex)"); - t.is (Lexer::typeName (Lexer::Type::string), "string", "Lexer::typeName (Lexer::Type::string)"); - t.is (Lexer::typeName (Lexer::Type::url), "url", "Lexer::typeName (Lexer::Type::url)"); - t.is (Lexer::typeName (Lexer::Type::pair), "pair", "Lexer::typeName (Lexer::Type::pair)"); - t.is (Lexer::typeName (Lexer::Type::set), "set", "Lexer::typeName (Lexer::Type::set)"); - t.is (Lexer::typeName (Lexer::Type::separator), "separator", "Lexer::typeName (Lexer::Type::separator)"); - t.is (Lexer::typeName (Lexer::Type::tag), "tag", "Lexer::typeName (Lexer::Type::tag)"); - t.is (Lexer::typeName (Lexer::Type::path), "path", "Lexer::typeName (Lexer::Type::path)"); - t.is (Lexer::typeName (Lexer::Type::substitution), "substitution", "Lexer::typeName (Lexer::Type::substitution)"); - t.is (Lexer::typeName (Lexer::Type::pattern), "pattern", "Lexer::typeName (Lexer::Type::pattern)"); - t.is (Lexer::typeName (Lexer::Type::op), "op", "Lexer::typeName (Lexer::Type::op)"); - t.is (Lexer::typeName (Lexer::Type::dom), "dom", "Lexer::typeName (Lexer::Type::dom)"); - t.is (Lexer::typeName (Lexer::Type::identifier), "identifier", "Lexer::typeName (Lexer::Type::identifier)"); - t.is (Lexer::typeName (Lexer::Type::word), "word", "Lexer::typeName (Lexer::Type::word)"); - t.is (Lexer::typeName (Lexer::Type::date), "date", "Lexer::typeName (Lexer::Type::date)"); - t.is (Lexer::typeName (Lexer::Type::duration), "duration", "Lexer::typeName (Lexer::Type::duration)"); - - // std::string Lexer::lowerCase (const std::string& input) - t.is (Lexer::lowerCase (""), "", "Lexer::lowerCase '' -> ''"); - t.is (Lexer::lowerCase ("pre01_:POST"), "pre01_:post", "Lexer::lowerCase 'pre01_:POST' -> 'pre01_:post'"); - - // std::string Lexer::commify (const std::string& data) - t.is (Lexer::commify (""), "", "Lexer::commify '' -> ''"); - t.is (Lexer::commify ("1"), "1", "Lexer::commify '1' -> '1'"); - t.is (Lexer::commify ("12"), "12", "Lexer::commify '12' -> '12'"); - t.is (Lexer::commify ("123"), "123", "Lexer::commify '123' -> '123'"); - t.is (Lexer::commify ("1234"), "1,234", "Lexer::commify '1234' -> '1,234'"); - t.is (Lexer::commify ("12345"), "12,345", "Lexer::commify '12345' -> '12,345'"); - t.is (Lexer::commify ("123456"), "123,456", "Lexer::commify '123456' -> '123,456'"); - t.is (Lexer::commify ("1234567"), "1,234,567", "Lexer::commify '1234567' -> '1,234,567'"); - t.is (Lexer::commify ("12345678"), "12,345,678", "Lexer::commify '12345678' -> '12,345,678'"); - t.is (Lexer::commify ("123456789"), "123,456,789", "Lexer::commify '123456789' -> '123,456,789'"); - t.is (Lexer::commify ("1234567890"), "1,234,567,890", "Lexer::commify '1234567890' -> '1,234,567,890'"); - t.is (Lexer::commify ("1.0"), "1.0", "Lexer::commify '1.0' -> '1.0'"); - t.is (Lexer::commify ("12.0"), "12.0", "Lexer::commify '12.0' -> '12.0'"); - t.is (Lexer::commify ("123.0"), "123.0", "Lexer::commify '123.0' -> '123.0'"); - t.is (Lexer::commify ("1234.0"), "1,234.0", "Lexer::commify '1234.0' -> '1,234.0'"); - t.is (Lexer::commify ("12345.0"), "12,345.0", "Lexer::commify '12345.0' -> '12,345.0'"); - t.is (Lexer::commify ("123456.0"), "123,456.0", "Lexer::commify '123456.0' -> '123,456.0'"); - t.is (Lexer::commify ("1234567.0"), "1,234,567.0", "Lexer::commify '1234567.0' -> '1,234,567.0'"); - t.is (Lexer::commify ("12345678.0"), "12,345,678.0", "Lexer::commify '12345678.0' -> '12,345,678.0'"); - t.is (Lexer::commify ("123456789.0"), "123,456,789.0", "Lexer::commify '123456789.0' -> '123,456,789.0'"); - t.is (Lexer::commify ("1234567890.0"), "1,234,567,890.0", "Lexer::commify '1234567890.0' -> '1,234,567,890.0'"); - t.is (Lexer::commify ("pre"), "pre", "Lexer::commify 'pre' -> 'pre'"); - t.is (Lexer::commify ("pre1234"), "pre1,234", "Lexer::commify 'pre1234' -> 'pre1,234'"); - t.is (Lexer::commify ("1234post"), "1,234post", "Lexer::commify '1234post' -> '1,234post'"); - t.is (Lexer::commify ("pre1234post"), "pre1,234post", "Lexer::commify 'pre1234post' -> 'pre1,234post'"); - - // std::string Lexer::trimLeft (const std::string& in, const std::string& t /*= " "*/) - t.is (Lexer::trimLeft (""), "", "Lexer::trimLeft '' -> ''"); - t.is (Lexer::trimLeft (" "), "", "Lexer::trimLeft ' ' -> ''"); - t.is (Lexer::trimLeft ("", " \t"), "", "Lexer::trimLeft '' -> ''"); - t.is (Lexer::trimLeft ("xxx"), "xxx", "Lexer::trimLeft 'xxx' -> 'xxx'"); - t.is (Lexer::trimLeft ("xxx", " \t"), "xxx", "Lexer::trimLeft 'xxx' -> 'xxx'"); - t.is (Lexer::trimLeft (" \t xxx \t "), "\t xxx \t ",R"(Lexer::trimLeft ' \t xxx \t ' -> '\t xxx \t ')"); - t.is (Lexer::trimLeft (" \t xxx \t ", " \t"), "xxx \t ", R"(Lexer::trimLeft ' \t xxx \t ' -> 'xxx \t ')"); - - // std::string Lexer::trimRight (const std::string& in, const std::string& t /*= " "*/) - t.is (Lexer::trimRight (""), "", "Lexer::trimRight '' -> ''"); - t.is (Lexer::trimRight (" "), "", "Lexer::trimRight ' ' -> ''"); - t.is (Lexer::trimRight ("", " \t"), "", "Lexer::trimRight '' -> ''"); - t.is (Lexer::trimRight ("xxx"), "xxx", "Lexer::trimRight 'xxx' -> 'xxx'"); - t.is (Lexer::trimRight ("xxx", " \t"), "xxx", "Lexer::trimRight 'xxx' -> 'xxx'"); - t.is (Lexer::trimRight (" \t xxx \t "), " \t xxx \t", R"(Lexer::trimRight ' \t xxx \t ' -> ' \t xxx \t')"); - t.is (Lexer::trimRight (" \t xxx \t ", " \t"), " \t xxx", R"(Lexer::trimRight ' \t xxx \t ' -> ' \t xxx')"); - - // std::string Lexer::trim (const std::string& in, const std::string& t /*= " "*/) - t.is (Lexer::trim (""), "", "Lexer::trim '' -> ''"); - t.is (Lexer::trim (" "), "", "Lexer::trim ' ' -> ''"); - t.is (Lexer::trim ("", " \t"), "", "Lexer::trim '' -> ''"); - t.is (Lexer::trim ("xxx"), "xxx", "Lexer::trim 'xxx' -> 'xxx'"); - t.is (Lexer::trim ("xxx", " \t"), "xxx", "Lexer::trim 'xxx' -> 'xxx'"); - t.is (Lexer::trim (" \t xxx \t "), "\t xxx \t",R"(Lexer::trim ' \t xxx \t ' -> '\t xxx \t')"); - t.is (Lexer::trim (" \t xxx \t ", " \t"), "xxx", "Lexer::trim ' \\t xxx \\t ' -> 'xxx'"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/lexer_test.cpp b/test/lexer_test.cpp new file mode 100644 index 000000000..0189699bb --- /dev/null +++ b/test/lexer_test.cpp @@ -0,0 +1,1006 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { +#ifdef PRODUCT_TASKWARRIOR + UnitTest t(1255); +#else + UnitTest t(1235); +#endif + + // Use same Datetime/Duraiton configuration as Context∴:staticInitialization. + Datetime::isoEnabled = true; + Datetime::standaloneDateEnabled = false; + Datetime::standaloneTimeEnabled = false; + Duration::standaloneSecondsEnabled = false; + + std::vector> tokens; + std::string token; + Lexer::Type type; + + // Feed in some attributes and types, so that the Lexer knows what a DOM + // reference is. + Lexer::attributes["due"] = "date"; + Lexer::attributes["tags"] = "string"; + Lexer::attributes["description"] = "string"; + + // static bool Lexer::isBoundary (int, int); + t.ok(Lexer::isBoundary(' ', 'a'), "' ' --> 'a' = isBoundary"); + t.ok(Lexer::isBoundary('a', ' '), "'a' --> ' ' = isBoundary"); + t.ok(Lexer::isBoundary(' ', '+'), "' ' --> '+' = isBoundary"); + t.ok(Lexer::isBoundary(' ', ','), "' ' --> ',' = isBoundary"); + t.notok(Lexer::isBoundary('3', '4'), "'3' --> '4' = isBoundary"); + t.ok(Lexer::isBoundary('(', '('), "'(' --> '(' = isBoundary"); + t.notok(Lexer::isBoundary('r', 'd'), "'r' --> 'd' = isBoundary"); + + // static bool Lexer::wasQuoted (const std::string&); + t.notok(Lexer::wasQuoted(""), "'' --> !wasQuoted"); + t.notok(Lexer::wasQuoted("foo"), "'foo' --> !wasQuoted"); + t.ok(Lexer::wasQuoted("a b"), "'a b' --> wasQuoted"); + t.ok(Lexer::wasQuoted("(a)"), "'(a)' --> wasQuoted"); + + // static bool Lexer::dequote (std::string&, const std::string& quotes = "'\""); + token = "foo"; + Lexer::dequote(token); + t.is(token, "foo", "dequote foo --> foo"); + + token = "'foo'"; + Lexer::dequote(token); + t.is(token, "foo", "dequote 'foo' --> foo"); + + token = "'o\\'clock'"; + Lexer::dequote(token); + t.is(token, "o\\'clock", "dequote 'o\\'clock' --> o\\'clock"); + + token = "abba"; + Lexer::dequote(token, "a"); + t.is(token, "bb", "dequote 'abba' (a) --> bb"); + + // Should result in no tokens. + Lexer l0(""); + t.notok(l0.token(token, type), "'' --> no tokens"); + + // Should result in no tokens. + Lexer l1(" \t "); + t.notok(l1.token(token, type), "' \\t ' --> no tokens"); + + // \u20ac = Euro symbol. + Lexer l2(R"( one 'two \'three\''+456-(1.3*2 - 0x12) 1.2e-3.4 foo.bar and '\u20ac')"); + + tokens.clear(); + while (l2.token(token, type)) { + std::cout << "# «" << token << "» " << Lexer::typeName(type) << "\n"; + tokens.emplace_back(token, type); + } + + t.is(tokens[0].first, "one", "tokens[0] = 'one'"); // 30 + t.is(Lexer::typeName(tokens[0].second), "identifier", "tokens[0] = identifier"); + t.is(tokens[1].first, "'two 'three''", "tokens[1] = 'two 'three''"); + t.is(Lexer::typeName(tokens[1].second), "string", "tokens[1] = string"); + t.is(tokens[2].first, "+", "tokens[2] = '+'"); + t.is(Lexer::typeName(tokens[2].second), "op", "tokens[2] = op"); + t.is(tokens[3].first, "456", "tokens[3] = '456'"); + t.is(Lexer::typeName(tokens[3].second), "number", "tokens[3] = number"); + t.is(tokens[4].first, "-", "tokens[4] = '-'"); + t.is(Lexer::typeName(tokens[4].second), "op", "tokens[4] = op"); + t.is(tokens[5].first, "(", "tokens[5] = '('"); // 40 + t.is(Lexer::typeName(tokens[5].second), "op", "tokens[5] = op"); + t.is(tokens[6].first, "1.3", "tokens[6] = '1.3'"); + t.is(Lexer::typeName(tokens[6].second), "number", "tokens[6] = number"); + t.is(tokens[7].first, "*", "tokens[7] = '*'"); + t.is(Lexer::typeName(tokens[7].second), "op", "tokens[7] = op"); + t.is(tokens[8].first, "2", "tokens[8] = '2'"); + t.is(Lexer::typeName(tokens[8].second), "number", "tokens[8] = number"); + t.is(tokens[9].first, "-", "tokens[9] = '-'"); + t.is(Lexer::typeName(tokens[9].second), "op", "tokens[9] = op"); + t.is(tokens[10].first, "0x12", "tokens[10] = '0x12'"); // 50 + t.is(Lexer::typeName(tokens[10].second), "hex", "tokens[10] = hex"); + t.is(tokens[11].first, ")", "tokens[11] = ')'"); + t.is(Lexer::typeName(tokens[11].second), "op", "tokens[11] = op"); + t.is(tokens[12].first, "1.2e-3.4", "tokens[12] = '1.2e-3.4'"); + t.is(Lexer::typeName(tokens[12].second), "number", "tokens[12] = number"); + t.is(tokens[13].first, "foo.bar", "tokens[13] = 'foo.bar'"); + t.is(Lexer::typeName(tokens[13].second), "identifier", "tokens[13] = identifier"); + t.is(tokens[14].first, "and", "tokens[14] = 'and'"); // 60 + t.is(Lexer::typeName(tokens[14].second), "op", "tokens[14] = op"); + t.is(tokens[15].first, "'€'", "tokens[15] = \\u20ac --> ''€''"); + t.is(Lexer::typeName(tokens[15].second), "string", "tokens[15] = string"); + + // Test for numbers that are no longer ISO-8601 dates. + Lexer l3("1 12 123 1234 12345 123456 1234567"); + tokens.clear(); + while (l3.token(token, type)) { + std::cout << "# «" << token << "» " << Lexer::typeName(type) << "\n"; + tokens.emplace_back(token, type); + } + + t.is((int)tokens.size(), 7, "7 tokens"); + t.is(tokens[0].first, "1", "tokens[0] == '1'"); + t.is((int)tokens[0].second, (int)Lexer::Type::number, "tokens[0] == Type::number"); + t.is(tokens[1].first, "12", "tokens[1] == '12'"); + t.is((int)tokens[1].second, (int)Lexer::Type::number, "tokens[1] == Type::number"); + t.is(tokens[2].first, "123", "tokens[2] == '123'"); + t.is((int)tokens[2].second, (int)Lexer::Type::number, "tokens[2] == Type::number"); // 70 + t.is(tokens[3].first, "1234", "tokens[3] == '1234'"); + t.is((int)tokens[3].second, (int)Lexer::Type::number, "tokens[3] == Type::number"); + t.is(tokens[4].first, "12345", "tokens[4] == '12345'"); + t.is((int)tokens[4].second, (int)Lexer::Type::number, "tokens[4] == Type::number"); + t.is(tokens[5].first, "123456", "tokens[5] == '123456'"); + t.is((int)tokens[5].second, (int)Lexer::Type::number, "tokens[5] == Type::number"); + t.is(tokens[6].first, "1234567", "tokens[6] == '1234567'"); + t.is((int)tokens[6].second, (int)Lexer::Type::number, "tokens[6] == Type::number"); + + // void split (std::vector&, const std::string&); + std::string unsplit = " ( A or B ) "; + std::vector items; + items = Lexer::split(unsplit); + t.is(items.size(), (size_t)5, "split ' ( A or B ) '"); + t.is(items[0], "(", "split ' ( A or B ) ' -> [0] '('"); + t.is(items[1], "A", "split ' ( A or B ) ' -> [1] 'A'"); + t.is(items[2], "or", "split ' ( A or B ) ' -> [2] 'or'"); + t.is(items[3], "B", "split ' ( A or B ) ' -> [3] 'B'"); + t.is(items[4], ")", "split ' ( A or B ) ' -> [4] ')'"); + + // Test simple mode with contrived tokens that ordinarily split. + unsplit = " +-* a+b 12.3e4 'c d'"; + items = Lexer::split(unsplit); + t.is(items.size(), (size_t)8, "split ' +-* a+b 12.3e4 'c d''"); + t.is(items[0], "+", "split ' +-* a+b 12.3e4 'c d'' -> [0] '+'"); + t.is(items[1], "-", "split ' +-* a+b 12.3e4 'c d'' -> [1] '-'"); + t.is(items[2], "*", "split ' +-* a+b 12.3e4 'c d'' -> [2] '*'"); + t.is(items[3], "a", "split ' +-* a+b 12.3e4 'c d'' -> [3] 'a'"); + t.is(items[4], "+", "split ' +-* a+b 12.3e4 'c d'' -> [4] '+'"); + t.is(items[5], "b", "split ' +-* a+b 12.3e4 'c d'' -> [5] 'b'"); + t.is(items[6], "12.3e4", "split ' +-* a+b 12.3e4 'c d'' -> [6] '12.3e4'"); + t.is(items[7], "'c d'", "split ' +-* a+b 12.3e4 'c d'' -> [7] ''c d''"); + + // static bool decomposePair (const std::string&, std::string&, std::string&, std::string&, + // std::string&); 2 * 4 * 2 * 5 = 80 tests. + std::string outName, outMod, outValue, outSep; + for (auto& name : {"name"}) { + for (auto& mod : {"", "mod"}) { + for (auto& sep : {":", "=", "::", ":="}) { + for (auto& value : {"", "value", "a:b", "a::b", "a=b", "a:=b"}) { + std::string input = std::string("name") + (strlen(mod) ? "." : "") + mod + sep + value; + t.ok(Lexer::decomposePair(input, outName, outMod, outSep, outValue), + "decomposePair '" + input + "' --> true"); + t.is(name, outName, " '" + input + "' --> name '" + name + "'"); + t.is(mod, outMod, " '" + input + "' --> mod '" + mod + "'"); + t.is(value, outValue, " '" + input + "' --> value '" + value + "'"); + t.is(sep, outSep, " '" + input + "' --> sep '" + sep + "'"); + } + } + } + } + + // static bool readWord (const std::string&, const std::string&, std::string::size_type&, + // std::string&); + std::string::size_type cursor = 0; + std::string word; + t.ok(Lexer::readWord("'one two'", "'\"", cursor, word), "readWord ''one two'' --> true"); + t.is(word, "'one two'", " word '" + word + "'"); + t.is((int)cursor, 9, " cursor"); + + // Unterminated quoted string is invalid. + cursor = 0; + t.notok(Lexer::readWord("'one", "'\"", cursor, word), "readWord ''one' --> false"); + + // static bool readWord (const std::string&, std::string::size_type&, std::string&); + cursor = 0; + t.ok(Lexer::readWord("input", cursor, word), "readWord 'input' --> true"); + t.is(word, "input", " word '" + word + "'"); + t.is((int)cursor, 5, " cursor"); + + cursor = 0; + t.ok(Lexer::readWord("one\\ two", cursor, word), "readWord 'one\\ two' --> true"); + t.is(word, "one two", " word '" + word + "'"); + t.is((int)cursor, 8, " cursor"); + + cursor = 0; + t.ok(Lexer::readWord("\\u20A43", cursor, word), "readWord '\\u20A43' --> true"); + t.is(word, "₤3", " word '" + word + "'"); + t.is((int)cursor, 7, " cursor"); + + cursor = 0; + t.ok(Lexer::readWord("U+20AC4", cursor, word), "readWord '\\u20AC4' --> true"); + t.is(word, "€4", " word '" + word + "'"); + t.is((int)cursor, 7, " cursor"); + + std::string text = "one 'two' three\\ four"; + cursor = 0; + t.ok(Lexer::readWord(text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); + t.is(word, "one", " word '" + word + "'"); + cursor++; + t.ok(Lexer::readWord(text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); + t.is(word, "'two'", " word '" + word + "'"); + cursor++; + t.ok(Lexer::readWord(text, cursor, word), R"(readWord "one 'two' three\ four" --> true)"); + t.is(word, "three four", " word '" + word + "'"); + + text = "one "; + cursor = 0; + t.ok(Lexer::readWord(text, cursor, word), "readWord \"one \" --> true"); + t.is(word, "one", " word '" + word + "'"); + + // bool isLiteral (const std::string&, bool, bool); + Lexer l4("one.two"); + t.notok(l4.isLiteral("zero", false, false), "isLiteral 'one.two' --> false"); + t.ok(l4.isLiteral("one", false, false), "isLiteral 'one.two' --> 'one'"); + t.ok(l4.isLiteral(".", false, false), "isLiteral 'one.two' --> '.'"); + t.ok(l4.isLiteral("two", false, true), "isLiteral 'one.two' --> 'two'"); + + Lexer l5("wonder"); + t.notok(l5.isLiteral("wonderful", false, false), + "isLiteral 'wonderful' != 'wonder' without abbreviation"); + t.ok(l5.isLiteral("wonderful", true, false), + "isLiteral 'wonderful' == 'wonder' with abbreviation"); + + // bool isOneOf (const std::string&, bool, bool); + Lexer l6("Grumpy."); + std::vector dwarves = {"Sneezy", "Doc", "Bashful", "Grumpy", + "Happy", "Sleepy", "Dopey"}; + t.notok(l6.isOneOf(dwarves, false, true), "isOneof ('Grumpy', true) --> false"); + t.ok(l6.isOneOf(dwarves, false, false), "isOneOf ('Grumpy', false) --> true"); + + // static std::string::size_type commonLength (const std::string&, const std::string&); + t.is((int)Lexer::commonLength("", ""), 0, "commonLength '' : '' --> 0"); + t.is((int)Lexer::commonLength("a", "a"), 1, "commonLength 'a' : 'a' --> 1"); + t.is((int)Lexer::commonLength("abcde", "abcde"), 5, "commonLength 'abcde' : 'abcde' --> 5"); + t.is((int)Lexer::commonLength("abc", ""), 0, "commonLength 'abc' : '' --> 0"); + t.is((int)Lexer::commonLength("abc", "def"), 0, "commonLength 'abc' : 'def' --> 0"); + t.is((int)Lexer::commonLength("foobar", "foo"), 3, "commonLength 'foobar' : 'foo' --> 3"); + t.is((int)Lexer::commonLength("foo", "foobar"), 3, "commonLength 'foo' : 'foobar' --> 3"); + + // static std::string::size_type commonLength (const std::string&, std::string::size_type, const + // std::string&, std::string::size_type); + t.is((int)Lexer::commonLength("wonder", 0, "prowonderbread", 3), 6, + "'wonder'+0 : 'prowonderbread'+3 --> 6"); + +// Test all Lexer types. +#define NO \ + { \ + "", Lexer::Type::word \ + } + struct { + const char* input; + struct { + const char* token; + Lexer::Type type; + bool expfail_token = false; + bool expfail_type = false; + } results[5]; + } lexerTests[] = { + // Pattern + { + "/foo/", + {{"/foo/", Lexer::Type::pattern}, NO, NO, NO, NO}, + }, + { + "/a\\/b/", + {{"/a\\/b/", Lexer::Type::pattern}, NO, NO, NO, NO}, + }, + { + "/'/", + {{"/'/", Lexer::Type::pattern}, NO, NO, NO, NO}, + }, + + // Substitution + { + "/from/to/g", + {{"/from/to/g", Lexer::Type::substitution}, NO, NO, NO, NO}, + }, + { + "/from/to/", + {{"/from/to/", Lexer::Type::substitution}, NO, NO, NO, NO}, + }, + + // Tag + { + "+tag", + {{"+tag", Lexer::Type::tag}, NO, NO, NO, NO}, + }, + { + "-tag", + {{"-tag", Lexer::Type::tag}, NO, NO, NO, NO}, + }, + { + "+@tag", + {{"+@tag", Lexer::Type::tag}, NO, NO, NO, NO}, + }, + + // Path + { + "/long/path/to/file.txt", + {{"/long/path/to/file.txt", Lexer::Type::path}, NO, NO, NO, NO}, + }, + + // Word + { + "1.foo.bar", + {{"1.foo.bar", Lexer::Type::word}, NO, NO, NO, NO}, + }, + + // Identifier + { + "foo", + {{"foo", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "Çirçös", + {{"Çirçös", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "☺", + {{"☺", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "name", + {{"name", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "f1", + {{"f1", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "foo.bar", + {{"foo.bar", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + { + "a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", + {{"a1a1a1a1_a1a1_a1a1_a1a1_a1a1a1a1a1a1", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + + // Word that starts wih 'or', which is an operator, but should be ignored. + { + "ordinary", + {{"ordinary", Lexer::Type::identifier}, NO, NO, NO, NO}, + }, + + // DOM + { + "due", + {{"due", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.tags", + {{"123.tags", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.tags.PENDING", + {{"123.tags.PENDING", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.description", + {{"123.description", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.annotations.1.description", + {{"123.annotations.1.description", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.annotations.1.entry", + {{"123.annotations.1.entry", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "123.annotations.1.entry.year", + {{"123.annotations.1.entry.year", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c-4366-b70c-ea7e7520b749.due", + {{"a360fc44-315c-4366-b70c-ea7e7520b749.due", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "12345678-1234-1234-1234-123456789012.due", + {{"12345678-1234-1234-1234-123456789012.due", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "system.os", + {{"system.os", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + { + "rc.foo", + {{"rc.foo", Lexer::Type::dom}, NO, NO, NO, NO}, + }, + + // URL + { + "http://example.com", + {{"http://example.com", Lexer::Type::url}, NO, NO, NO, NO}, + }, + { + "https://foo.example.com", + {{"https://foo.example.com", Lexer::Type::url}, NO, NO, NO, NO}, + }, + + // String + { + "'one two'", + {{"'one two'", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + "\"three\"", + {{"\"three\"", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + "'\\''", + {{"'''", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + R"("\"")", + {{R"(""")", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + "\"\tfoo\t\"", + {{"\"\tfoo\t\"", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + R"("\u20A43")", + {{"\"₤3\"", Lexer::Type::string}, NO, NO, NO, NO}, + }, + { + "\"U+20AC4\"", + {{"\"€4\"", Lexer::Type::string}, NO, NO, NO, NO}, + }, + + // Number + { + "1", + {{"1", Lexer::Type::number}, NO, NO, NO, NO}, + }, + { + "3.14", + {{"3.14", Lexer::Type::number}, NO, NO, NO, NO}, + }, + { + "6.02217e23", + {{"6.02217e23", Lexer::Type::number}, NO, NO, NO, NO}, + }, + { + "1.2e-3.4", + {{"1.2e-3.4", Lexer::Type::number}, NO, NO, NO, NO}, + }, + { + "0x2f", + {{"0x2f", Lexer::Type::hex}, NO, NO, NO, NO}, + }, + + // Set (1,2,4-7,9) + { + "1,2", + {{"1,2", Lexer::Type::set}, NO, NO, NO, NO}, + }, + { + "1-2", + {{"1-2", Lexer::Type::set}, NO, NO, NO, NO}, + }, + { + "1-2,4", + {{"1-2,4", Lexer::Type::set}, NO, NO, NO, NO}, + }, + { + "1-2,4,6-8", + {{"1-2,4,6-8", Lexer::Type::set}, NO, NO, NO, NO}, + }, + { + "1-2,4,6-8,10-12", + {{"1-2,4,6-8,10-12", Lexer::Type::set}, NO, NO, NO, NO}, + }, + + // Pair + { + "name:value", + {{"name:value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name=value", + {{"name=value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name:=value", + {{"name:=value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name.mod:value", + {{"name.mod:value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name.mod=value", + {{"name.mod=value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name:", + {{"name:", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name=", + {{"name=", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name.mod:", + {{"name.mod:", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name.mod=", + {{"name.mod=", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "pro:'P 1'", + {{"pro:'P 1'", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "rc:x", + {{"rc:x", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "rc.name:value", + {{"rc.name:value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "rc.name=value", + {{"rc.name=value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "rc.name:=value", + {{"rc.name:=value", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "due:='eow - 2d'", + {{"due:='eow - 2d'", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + { + "name:'foo\nbar'", + {{"name:'foo\nbar'", Lexer::Type::pair}, NO, NO, NO, NO}, + }, + + // Operator - complete set + { + "^", + {{"^", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "!", + {{"!", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "_neg_", + {{"_neg_", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "_pos_", + {{"_pos_", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "_hastag_", + {{"_hastag_", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "_notag_", + {{"_notag_", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "*", + {{"*", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "/", + {{"/", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "%", + {{"%", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "+", + {{"+", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "-", + {{"-", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "<=", + {{"<=", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + ">=", + {{">=", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + ">", + {{">", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "<", + {{"<", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "=", + {{"=", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "==", + {{"==", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "!=", + {{"!=", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "!==", + {{"!==", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "~", + {{"~", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "!~", + {{"!~", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "and", + {{"and", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "or", + {{"or", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "xor", + {{"xor", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + "(", + {{"(", Lexer::Type::op}, NO, NO, NO, NO}, + }, + { + ")", + {{")", Lexer::Type::op}, NO, NO, NO, NO}, + }, + + // UUID + { + "ffffffff-ffff-ffff-ffff-ffffffffffff", + {{"ffffffff-ffff-ffff-ffff-ffffffffffff", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "0000000d-0000-0000-0000-000000000000", + {{"0000000d-0000-0000-0000-000000000000", Lexer::Type::uuid, true, true}, NO, NO, NO, NO}, + }, + { + "00000000-0000-0000-0000-0000000", + {{"00000000-0000-0000-0000-0000000", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "00000000-0000-0000-0000", + {{"00000000-0000-0000-0000", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "00000000-0000-0000", + {{"00000000-0000-0000", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "00000000-0000", + {{"00000000-0000", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "00000000", + {{"00000000", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c-4366-b70c-ea7e7520b749", + {{"a360fc44-315c-4366-b70c-ea7e7520b749", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c-4366-b70c-ea7e752", + {{"a360fc44-315c-4366-b70c-ea7e752", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c-4366-b70c", + {{"a360fc44-315c-4366-b70c", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c-4366", + {{"a360fc44-315c-4366", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44-315c", + {{"a360fc44-315c", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + { + "a360fc44", + {{"a360fc44", Lexer::Type::uuid}, NO, NO, NO, NO}, + }, + + // Date + { + "2015-W01", + {{"2015-W01", Lexer::Type::date}, NO, NO, NO, NO}, + }, + { + "2015-02-17", + {{"2015-02-17", Lexer::Type::date}, NO, NO, NO, NO}, + }, + { + "2013-11-29T22:58:00Z", + {{"2013-11-29T22:58:00Z", Lexer::Type::date}, NO, NO, NO, NO}, + }, + { + "20131129T225800Z", + {{"20131129T225800Z", Lexer::Type::date}, NO, NO, NO, NO}, + }, +#ifdef PRODUCT_TASKWARRIOR + { + "9th", + {{"9th", Lexer::Type::date}, NO, NO, NO, NO}, + }, + { + "10th", + {{"10th", Lexer::Type::date}, NO, NO, NO, NO}, + }, + { + "today", + {{"today", Lexer::Type::date}, NO, NO, NO, NO}, + }, +#endif + + // Duration + { + "year", + {{"year", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "4weeks", + {{"4weeks", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "PT23H", + {{"PT23H", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "1second", + {{"1second", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "1s", + {{"1s", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "1minute", + {{"1minute", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "2hour", + {{"2hour", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "3 days", + {{"3 days", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "4w", + {{"4w", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "5mo", + {{"5mo", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "6 years", + {{"6 years", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "P1Y", + {{"P1Y", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "PT1H", + {{"PT1H", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + { + "P1Y1M1DT1H1M1S", + {{"P1Y1M1DT1H1M1S", Lexer::Type::duration}, NO, NO, NO, NO}, + }, + + // Misc + { + "--", + {{"--", Lexer::Type::separator}, NO, NO, NO, NO}, + }, + + // Expression + // due:eom-2w + // due < eom + 1w + 1d + // ( /pattern/ or 8ad2e3db-914d-4832-b0e6-72fa04f6e331,3b6218f9-726a-44fc-aa63-889ff52be442 + // ) + { + "(1+2)", + { + {"(", Lexer::Type::op}, + {"1", Lexer::Type::number}, + {"+", Lexer::Type::op}, + {"2", Lexer::Type::number}, + {")", Lexer::Type::op}, + }, + }, + { + "description~pattern", + {{"description", Lexer::Type::dom}, + {"~", Lexer::Type::op}, + {"pattern", Lexer::Type::identifier}, + NO, + NO}, + }, + { + "(+tag)", + {{"(", Lexer::Type::op}, {"+tag", Lexer::Type::tag}, {")", Lexer::Type::op}, NO, NO}, + }, + { + "(name:value)", + {{"(", Lexer::Type::op}, + {"name:value", Lexer::Type::pair}, + {")", Lexer::Type::op}, + NO, + NO}, + }, + }; + + for (const auto& lexerTest : lexerTests) { + // The isolated test puts the input string directly into the Lexer. + Lexer isolated(lexerTest.input); + + for (const auto& result : lexerTest.results) { + if (result.token[0]) { + // Isolated: "" + t.ok(isolated.token(token, type), "Isolated Lexer::token(...) --> true"); + t.is(token, result.token, " token --> " + token, result.expfail_token); + t.is((int)type, (int)result.type, " type --> Lexer::Type::" + Lexer::typeToString(type), + result.expfail_type); + } + } + + // The embedded test surrounds the input string with a space. + Lexer embedded(std::string(" ") + lexerTest.input + " "); + + for (const auto& result : lexerTest.results) { + if (result.token[0]) { + // Embedded: "" + t.ok(embedded.token(token, type), "Embedded Lexer::token(...) --> true"); + t.is(token, result.token, " token --> " + token, result.expfail_token); + t.is((int)type, (int)result.type, " type --> Lexer::Type::" + Lexer::typeToString(type), + result.expfail_type); + } + } + } + + t.is(Lexer::typeName(Lexer::Type::uuid), "uuid", "Lexer::typeName (Lexer::Type::uuid)"); + t.is(Lexer::typeName(Lexer::Type::number), "number", "Lexer::typeName (Lexer::Type::number)"); + t.is(Lexer::typeName(Lexer::Type::hex), "hex", "Lexer::typeName (Lexer::Type::hex)"); + t.is(Lexer::typeName(Lexer::Type::string), "string", "Lexer::typeName (Lexer::Type::string)"); + t.is(Lexer::typeName(Lexer::Type::url), "url", "Lexer::typeName (Lexer::Type::url)"); + t.is(Lexer::typeName(Lexer::Type::pair), "pair", "Lexer::typeName (Lexer::Type::pair)"); + t.is(Lexer::typeName(Lexer::Type::set), "set", "Lexer::typeName (Lexer::Type::set)"); + t.is(Lexer::typeName(Lexer::Type::separator), "separator", + "Lexer::typeName (Lexer::Type::separator)"); + t.is(Lexer::typeName(Lexer::Type::tag), "tag", "Lexer::typeName (Lexer::Type::tag)"); + t.is(Lexer::typeName(Lexer::Type::path), "path", "Lexer::typeName (Lexer::Type::path)"); + t.is(Lexer::typeName(Lexer::Type::substitution), "substitution", + "Lexer::typeName (Lexer::Type::substitution)"); + t.is(Lexer::typeName(Lexer::Type::pattern), "pattern", "Lexer::typeName (Lexer::Type::pattern)"); + t.is(Lexer::typeName(Lexer::Type::op), "op", "Lexer::typeName (Lexer::Type::op)"); + t.is(Lexer::typeName(Lexer::Type::dom), "dom", "Lexer::typeName (Lexer::Type::dom)"); + t.is(Lexer::typeName(Lexer::Type::identifier), "identifier", + "Lexer::typeName (Lexer::Type::identifier)"); + t.is(Lexer::typeName(Lexer::Type::word), "word", "Lexer::typeName (Lexer::Type::word)"); + t.is(Lexer::typeName(Lexer::Type::date), "date", "Lexer::typeName (Lexer::Type::date)"); + t.is(Lexer::typeName(Lexer::Type::duration), "duration", + "Lexer::typeName (Lexer::Type::duration)"); + + // std::string Lexer::lowerCase (const std::string& input) + t.is(Lexer::lowerCase(""), "", "Lexer::lowerCase '' -> ''"); + t.is(Lexer::lowerCase("pre01_:POST"), "pre01_:post", + "Lexer::lowerCase 'pre01_:POST' -> 'pre01_:post'"); + + // std::string Lexer::commify (const std::string& data) + t.is(Lexer::commify(""), "", "Lexer::commify '' -> ''"); + t.is(Lexer::commify("1"), "1", "Lexer::commify '1' -> '1'"); + t.is(Lexer::commify("12"), "12", "Lexer::commify '12' -> '12'"); + t.is(Lexer::commify("123"), "123", "Lexer::commify '123' -> '123'"); + t.is(Lexer::commify("1234"), "1,234", "Lexer::commify '1234' -> '1,234'"); + t.is(Lexer::commify("12345"), "12,345", "Lexer::commify '12345' -> '12,345'"); + t.is(Lexer::commify("123456"), "123,456", "Lexer::commify '123456' -> '123,456'"); + t.is(Lexer::commify("1234567"), "1,234,567", "Lexer::commify '1234567' -> '1,234,567'"); + t.is(Lexer::commify("12345678"), "12,345,678", "Lexer::commify '12345678' -> '12,345,678'"); + t.is(Lexer::commify("123456789"), "123,456,789", "Lexer::commify '123456789' -> '123,456,789'"); + t.is(Lexer::commify("1234567890"), "1,234,567,890", + "Lexer::commify '1234567890' -> '1,234,567,890'"); + t.is(Lexer::commify("1.0"), "1.0", "Lexer::commify '1.0' -> '1.0'"); + t.is(Lexer::commify("12.0"), "12.0", "Lexer::commify '12.0' -> '12.0'"); + t.is(Lexer::commify("123.0"), "123.0", "Lexer::commify '123.0' -> '123.0'"); + t.is(Lexer::commify("1234.0"), "1,234.0", "Lexer::commify '1234.0' -> '1,234.0'"); + t.is(Lexer::commify("12345.0"), "12,345.0", "Lexer::commify '12345.0' -> '12,345.0'"); + t.is(Lexer::commify("123456.0"), "123,456.0", "Lexer::commify '123456.0' -> '123,456.0'"); + t.is(Lexer::commify("1234567.0"), "1,234,567.0", "Lexer::commify '1234567.0' -> '1,234,567.0'"); + t.is(Lexer::commify("12345678.0"), "12,345,678.0", + "Lexer::commify '12345678.0' -> '12,345,678.0'"); + t.is(Lexer::commify("123456789.0"), "123,456,789.0", + "Lexer::commify '123456789.0' -> '123,456,789.0'"); + t.is(Lexer::commify("1234567890.0"), "1,234,567,890.0", + "Lexer::commify '1234567890.0' -> '1,234,567,890.0'"); + t.is(Lexer::commify("pre"), "pre", "Lexer::commify 'pre' -> 'pre'"); + t.is(Lexer::commify("pre1234"), "pre1,234", "Lexer::commify 'pre1234' -> 'pre1,234'"); + t.is(Lexer::commify("1234post"), "1,234post", "Lexer::commify '1234post' -> '1,234post'"); + t.is(Lexer::commify("pre1234post"), "pre1,234post", + "Lexer::commify 'pre1234post' -> 'pre1,234post'"); + + // std::string Lexer::trimLeft (const std::string& in, const std::string& t /*= " "*/) + t.is(Lexer::trimLeft(""), "", "Lexer::trimLeft '' -> ''"); + t.is(Lexer::trimLeft(" "), "", "Lexer::trimLeft ' ' -> ''"); + t.is(Lexer::trimLeft("", " \t"), "", "Lexer::trimLeft '' -> ''"); + t.is(Lexer::trimLeft("xxx"), "xxx", "Lexer::trimLeft 'xxx' -> 'xxx'"); + t.is(Lexer::trimLeft("xxx", " \t"), "xxx", "Lexer::trimLeft 'xxx' -> 'xxx'"); + t.is(Lexer::trimLeft(" \t xxx \t "), "\t xxx \t ", + R"(Lexer::trimLeft ' \t xxx \t ' -> '\t xxx \t ')"); + t.is(Lexer::trimLeft(" \t xxx \t ", " \t"), "xxx \t ", + R"(Lexer::trimLeft ' \t xxx \t ' -> 'xxx \t ')"); + + // std::string Lexer::trimRight (const std::string& in, const std::string& t /*= " "*/) + t.is(Lexer::trimRight(""), "", "Lexer::trimRight '' -> ''"); + t.is(Lexer::trimRight(" "), "", "Lexer::trimRight ' ' -> ''"); + t.is(Lexer::trimRight("", " \t"), "", "Lexer::trimRight '' -> ''"); + t.is(Lexer::trimRight("xxx"), "xxx", "Lexer::trimRight 'xxx' -> 'xxx'"); + t.is(Lexer::trimRight("xxx", " \t"), "xxx", "Lexer::trimRight 'xxx' -> 'xxx'"); + t.is(Lexer::trimRight(" \t xxx \t "), " \t xxx \t", + R"(Lexer::trimRight ' \t xxx \t ' -> ' \t xxx \t')"); + t.is(Lexer::trimRight(" \t xxx \t ", " \t"), " \t xxx", + R"(Lexer::trimRight ' \t xxx \t ' -> ' \t xxx')"); + + // std::string Lexer::trim (const std::string& in, const std::string& t /*= " "*/) + t.is(Lexer::trim(""), "", "Lexer::trim '' -> ''"); + t.is(Lexer::trim(" "), "", "Lexer::trim ' ' -> ''"); + t.is(Lexer::trim("", " \t"), "", "Lexer::trim '' -> ''"); + t.is(Lexer::trim("xxx"), "xxx", "Lexer::trim 'xxx' -> 'xxx'"); + t.is(Lexer::trim("xxx", " \t"), "xxx", "Lexer::trim 'xxx' -> 'xxx'"); + t.is(Lexer::trim(" \t xxx \t "), "\t xxx \t", R"(Lexer::trim ' \t xxx \t ' -> '\t xxx \t')"); + t.is(Lexer::trim(" \t xxx \t ", " \t"), "xxx", "Lexer::trim ' \\t xxx \\t ' -> 'xxx'"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/limit.t b/test/limit.test.py similarity index 99% rename from test/limit.t rename to test/limit.test.py index cf7849e8b..63e505f7c 100755 --- a/test/limit.t +++ b/test/limit.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -102,6 +102,7 @@ class TestLimit(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/list.all.projects.t b/test/list.all.projects.test.py similarity index 99% rename from test/list.all.projects.t rename to test/list.all.projects.test.py index ef49c41d4..a9248646d 100755 --- a/test/list.all.projects.t +++ b/test/list.all.projects.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,11 +28,13 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase + class TestListAllProjects(TestCase): @classmethod def setUpClass(cls): @@ -57,6 +58,7 @@ class TestListAllProjects(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/log.t b/test/log.test.py similarity index 97% rename from test/log.t rename to test/log.test.py index 6737ba5e7..946a74a02 100755 --- a/test/log.t +++ b/test/log.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -65,7 +65,7 @@ class TestBug1575(TestCase): def test_spurious_whitespace_in_url(self): """1575: ensure that extra whitespace does not get inserted into a URL. - tw-1575: `task log` mangles URLs when quoted + tw-1575: `task log` mangles URLs when quoted """ self.t("log testing123 https://foo.example.com") @@ -75,6 +75,7 @@ class TestBug1575(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/logo.t b/test/logo.test.py similarity index 96% rename from test/logo.t rename to test/logo.test.py index a1a257682..9467d8e56 100755 --- a/test/logo.t +++ b/test/logo.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -43,7 +43,7 @@ class TestLogoCommand(TestCase): def test_logo_command(self): """Check that there are colors. For coverage""" code, out, err = self.t("logo rc._forcecolor:on") - self.assertRegex(out, ".\[48;5;\d+m .\[0m") + self.assertRegex(out, r".\[48;5;\d+m .\[0m") def test_logo_command_no_color(self): """Check that it only works with color. For coverage""" @@ -53,6 +53,7 @@ class TestLogoCommand(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/make_tc_task.cpp b/test/make_tc_task.cpp new file mode 100644 index 000000000..37f1d963f --- /dev/null +++ b/test/make_tc_task.cpp @@ -0,0 +1,76 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2025, Dustin J. Mitchell +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include + +#include + +namespace { + +//////////////////////////////////////////////////////////////////////////////// +int usage() { + std::cerr << "USAGE: make_tc_task DATADIR KEY=VALUE ..\n"; + return 1; +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +int main(int argc, char **argv) { + if (!--argc) { + return usage(); + } + char *datadir = *++argv; + + auto replica = tc::new_replica_on_disk(datadir, /*create_if_missing=*/true, /*read_write=*/true); + auto uuid = tc::uuid_v4(); + auto operations = tc::new_operations(); + auto task = tc::create_task(uuid, operations); + + while (--argc) { + std::string arg = *++argv; + size_t eq_idx = arg.find('='); + if (eq_idx == std::string::npos) { + return usage(); + } + std::string property = arg.substr(0, eq_idx); + std::string value = arg.substr(eq_idx + 1); + task->update(property, value, operations); + } + replica->commit_operations(std::move(operations)); + + std::cout << static_cast(uuid.to_string()) << "\n"; + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/math.t b/test/math.test.py similarity index 87% rename from test/math.t rename to test/math.test.py index d071a33f7..13d97562d 100755 --- a/test/math.t +++ b/test/math.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest from datetime import datetime + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -52,64 +52,66 @@ class TestMath(TestCase): cls.t("add three 'due:eoy-10days'") cls.t("add four due:'eoy - 10days'") cls.t("add five 'due:eoy - 10days'") - cls.t("add six 'due:{}-12-31T23:59:59 - 10days'".format (datetime.now().year)) + cls.t("add six 'due:{}-12-31T23:59:59 - 10days'".format(datetime.now().year)) def test_compact_unquoted(self): """compact unquoted""" - code, out, err = self.t('_get 1.due') + code, out, err = self.t("_get 1.due") self.assertEqual(out, self.when) def test_compact_value_quoted(self): """compact value quoted""" - code, out, err = self.t('_get 2.due') + code, out, err = self.t("_get 2.due") self.assertEqual(out, self.when) def test_compact_arg_quoted(self): """compact arg quoted""" - code, out, err = self.t('_get 3.due') + code, out, err = self.t("_get 3.due") self.assertEqual(out, self.when) def test_sparse_value_quoted(self): """sparse value quoted""" - code, out, err = self.t('_get 4.due') + code, out, err = self.t("_get 4.due") self.assertEqual(out, self.when) def test_sparse_arg_quoted(self): """sparse arg quoted""" - code, out, err = self.t('_get 5.due') + code, out, err = self.t("_get 5.due") self.assertEqual(out, self.when) def test_sparse_arg_quoted_literal(self): """sparse arg quoted literal""" - code, out, err = self.t('_get 6.due') + code, out, err = self.t("_get 6.due") self.assertEqual(out, self.when) + class TestBug851(TestCase): @classmethod def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t('add past due:-2days') - cls.t('add future due:2days') + cls.t("add past due:-2days") + cls.t("add future due:2days") def setUp(self): """Executed before each test in the class""" def test_attribute_before_with_math(self): """851: Test due.before:now+1d""" - code, out, err = self.t('due.before:now+1day ls') + code, out, err = self.t("due.before:now+1day ls") self.assertIn("past", out) self.assertNotIn("future", out) def test_attribute_after_with_math(self): """851: Test due.after:now+1d""" - code, out, err = self.t('due.after:now+1day ls') + code, out, err = self.t("due.after:now+1day ls") self.assertNotIn("past", out) self.assertIn("future", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/modify.t b/test/modify.test.py similarity index 84% rename from test/modify.t rename to test/modify.test.py index c5a64b846..6b5fd55af 100755 --- a/test/modify.t +++ b/test/modify.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,10 +28,12 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase + class TestBug1306(TestCase): def setUp(self): self.t = Task() @@ -43,6 +44,7 @@ class TestBug1306(TestCase): code, out, err = self.t("1 info") self.assertIn("PROJ", out) + class TestBug1763(TestCase): def setUp(self): self.t = Task() @@ -53,8 +55,24 @@ class TestBug1763(TestCase): code, out, err = self.t("1 modify due:") self.assertIn("Modified 0 tasks.", out) + +class TestBug3584(TestCase): + def setUp(self): + self.t = Task() + + def test_mod_pending_task_end_date(self): + """Adding end date for a pending task throws an error""" + self.t("add foo") + code, out, err = self.t.runError("1 modify end:1d") + self.assertIn( + "Could not modify task 1. You cannot set an end date on a pending task.", + err, + ) + + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/nag.t b/test/nag.test.py similarity index 94% rename from test/nag.t rename to test/nag.test.py index 9edbe29fc..7b34da133 100755 --- a/test/nag.t +++ b/test/nag.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -77,16 +77,16 @@ class TestNagging(TestCase): def test_nagging_ready(self): """Verify that nagging occurs when there are READY tasks of higher urgency""" - self.t("add one") # low urgency - self.t("add two due:10days scheduled:yesterday") # medium urgency, ready + self.t("add one") # low urgency + self.t("add two due:10days scheduled:yesterday") # medium urgency, ready code, out, err = self.t("1 done") self.assertIn("NAG", err) def test_nagging_not_ready(self): """Verify that nagging does not occur when there are unREADY tasks of higher urgency""" - self.t("add one") # low urgency - self.t("add two due:10days scheduled:10days") # medium urgency, not ready + self.t("add one") # low urgency + self.t("add two due:10days scheduled:10days") # medium urgency, not ready code, out, err = self.t("1 done") self.assertNotIn("NAG", err) @@ -157,6 +157,7 @@ class TestNagging(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/news.test.py b/test/news.test.py new file mode 100755 index 000000000..7f1aa5dcf --- /dev/null +++ b/test/news.test.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2024, Adrian Sadłocha. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import os +import sys +import unittest + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestNewsNag(TestCase): + def setUp(self): + self.t = Task() + self.t("add Sample") + + def test_news_nag_gets_displayed_with_default_verbosity_levels(self): + """Default verbosity""" + + _, _, err = self.t("") + self.assertIn("Please run 'task news'", err) + + def test_news_nag_gets_displayed_when_explicitly_toggled_on(self): + """Explicitly toggled on""" + + # Add `footnote` so there is a sink for the nag message. + _, _, err = self.t("rc.verbose:news,footnote") + self.assertIn("Please run 'task news'", err) + + def test_news_nag_does_not_get_displayed_when_explicitly_toggled_off(self): + """Explicitly toggled off""" + + # Add `footnote` so there is a sink for the nag message. + _, _, err = self.t("rc.verbose:footnote") + self.assertNotIn("Please run 'task news'", err) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/obfuscate.t b/test/obfuscate.test.py similarity index 99% rename from test/obfuscate.t rename to test/obfuscate.test.py index 386e2a21a..5ff0e78f8 100755 --- a/test/obfuscate.t +++ b/test/obfuscate.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -75,6 +75,7 @@ class TestObfuscation(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/oldest.t b/test/oldest.test.py similarity index 99% rename from test/oldest.t rename to test/oldest.test.py index 527de4a7d..593a4e041 100755 --- a/test/oldest.t +++ b/test/oldest.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -118,6 +118,7 @@ class TestOldestAndNewest(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/operators.t b/test/operators.test.py similarity index 99% rename from test/operators.t rename to test/operators.test.py index aeede1905..fcf4554d2 100755 --- a/test/operators.t +++ b/test/operators.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -442,6 +442,7 @@ class TestOperatorsQuantity(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/overdue.t b/test/overdue.test.py similarity index 98% rename from test/overdue.t rename to test/overdue.test.py index 48e399af4..ac98c9279 100755 --- a/test/overdue.t +++ b/test/overdue.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -56,6 +56,7 @@ class TestOverdue(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/partial.t b/test/partial.test.py similarity index 90% rename from test/partial.t rename to test/partial.test.py index 7ef957d45..193f567f2 100755 --- a/test/partial.t +++ b/test/partial.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,17 +42,18 @@ class TestPartialMatch(TestCase): def test_partial_date_match_spaced(self): """Partial match for dates: today = now --> true""" - code, out, err = self.t('calc today = now') - self.assertIn('true', out) + code, out, err = self.t("calc today = now") + self.assertIn("true", out) def test_exact_date_match_spaced(self): """Exact match for dates: today == now --> false""" - code, out, err = self.t('calc today == now') - self.assertIn('false', out) + code, out, err = self.t("calc today == now") + self.assertIn("false", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/prepend.t b/test/prepend.test.py similarity index 99% rename from test/prepend.t rename to test/prepend.test.py index cc1e1f231..2215ddbc2 100755 --- a/test/prepend.t +++ b/test/prepend.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -63,6 +63,7 @@ class TestPrepend(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/pri_sort.t b/test/pri_sort.test.py similarity index 99% rename from test/pri_sort.t rename to test/pri_sort.test.py index e17ca9b46..25a19caf6 100755 --- a/test/pri_sort.t +++ b/test/pri_sort.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -111,6 +111,7 @@ class TestPrioritySorting(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/problems b/test/problems deleted file mode 100755 index da3c2606f..000000000 --- a/test/problems +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 - -from __future__ import print_function -import sys -import re -import argparse -from collections import defaultdict - - -def color(text, c): - """ - Add color on the keyword that identifies the state of the test - """ - if sys.stdout.isatty(): - clear = "\033[0m" - - colors = { - "red": "\033[1m\033[91m", - "yellow": "\033[1m\033[93m", - "green": "\033[1m\033[92m", - } - return colors[c] + text + clear - else: - return text - - -def parse_args(): - parser = argparse.ArgumentParser(description="Report on test results") - parser.add_argument('--summary', action="store_true", - help="Display only the totals in each category") - parser.add_argument('tapfile', default="all.log", nargs="?", - help="File containing TAP output") - return parser.parse_args() - - -def print_category(tests): - if not cmd_args.summary: - for key in sorted(tests): - print("%-32s %4d" % (key, tests[key])) - - -def pad(i): - return " " * i - - -if __name__ == "__main__": - cmd_args = parse_args() - - errors = defaultdict(int) - skipped = defaultdict(int) - expected = defaultdict(int) - unexpected = defaultdict(int) - passed = defaultdict(int) - - file = re.compile(r"^# (?:./)?(\S+\.t)(?:\.exe)?$") - timestamp = re.compile(r"^# (\d+(?:\.\d+)?) ==>.*$") - - expected_fail = re.compile(r"^not ok.*?#\s*TODO", re.I) - unexpected_pass = re.compile(r"^not ok .*?#\s*FIXED", re.I) - skip = re.compile(r"^ok .*?#\s*skip", re.I) - ok = re.compile(r"^ok ", re.I) - not_ok = re.compile(r"^not ok", re.I) - comment = re.compile(r"^#") - plan = re.compile(r"^1..\d+\s*(?:#.*)?$") - - start = None - stop = None - - with open(cmd_args.tapfile) as fh: - for line in fh: - if start is None: - # First line contains the starting timestamp - start = float(timestamp.match(line).group(1)) - continue - - match = file.match(line) - if match: - filename = match.group(1) - - elif expected_fail.match(line): - expected[filename] += 1 - - elif unexpected_pass.match(line): - unexpected[filename] += 1 - - elif skip.match(line): - skipped[filename] += 1 - - # It's important these come last, since they're subpatterns of the above - - elif ok.match(line): - passed[filename] += 1 - - elif not_ok.match(line): - errors[filename] += 1 - - elif comment.match(line): - pass - - elif plan.match(line): - pass - - else: - # Uncomment if you want to see malformed things we caught as well... - # print(color("Malformed TAP (" + filename + "): " + line, "red")) - pass - - # Last line contains the ending timestamp - stop = float(timestamp.match(line).group(1)) - - v = "{0:>5d}" - passed_str = "Passed:" + pad(24) - passed_int = v.format(sum(passed.values())) - error_str = "Failed:" + pad(24) - error_int = v.format(sum(errors.values())) - unexpected_str = "Unexpected successes:" + pad(10) - unexpected_int = v.format(sum(unexpected.values())) - skipped_str = "Skipped:" + pad(23) - skipped_int = v.format(sum(skipped.values())) - expected_str = "Expected failures:" + pad(13) - expected_int = v.format(sum(expected.values())) - runtime_str = "Runtime:" + pad(20) - runtime_int = "{0:>8.2f} seconds".format(stop - start) - details_str = "For details run 'make problems'" - - if cmd_args.summary: - print(color(passed_str, "green"), passed_int) - print(color(error_str, "red"), error_int) - print(color(unexpected_str, "red"), unexpected_int) - print(color(skipped_str, "yellow"), skipped_int) - print(color(expected_str, "yellow"), expected_int) - print(runtime_str, runtime_int) - print(details_str) - - else: - print(color(error_str, "red")) - print_category(errors) - print() - print(color(unexpected_str, "red")) - print_category(unexpected) - print() - print(color(skipped_str, "yellow")) - print_category(skipped) - print() - print(color(expected_str, "yellow")) - print_category(expected) - - # If we encoutered any failures, return non-zero code - sys.exit(1 if int(error_int) or int(unexpected_int) else 0) diff --git a/test/project.t b/test/project.test.py similarity index 86% rename from test/project.t rename to test/project.test.py index 626102f2e..c4718866d 100755 --- a/test/project.t +++ b/test/project.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,8 +39,10 @@ class TestProjects(TestCase): def setUp(self): self.t = Task() - self.STATUS = ("The project '{0}' has changed\. " - "Project '{0}' is {1} complete \({2} remaining\)\.") + self.STATUS = ( + r"The project '{0}' has changed\. " + r"Project '{0}' is {1} complete \({2} remaining\)\." + ) def test_project_summary_count(self): """'task projects' shouldn't consider deleted tasks in summary. @@ -52,53 +54,44 @@ class TestProjects(TestCase): self.t("3 delete", input="y\n") code, out, err = self.t("project:B projects") - expected = "1 project \(1 task\)" + expected = r"1 project \(1 task\)" self.assertRegex(out, expected) def test_project_progress(self): """project status/progress is shown and is up-to-date""" code, out, err = self.t("add one pro:foo") - self.assertRegex(err, self.STATUS.format("foo", "0%", - "1 task")) + self.assertRegex(err, self.STATUS.format("foo", "0%", "1 task")) code, out, err = self.t("add two pro:foo") - self.assertRegex(err, self.STATUS.format("foo", "0%", - "2 of 2 tasks")) + self.assertRegex(err, self.STATUS.format("foo", "0%", "2 of 2 tasks")) code, out, err = self.t("add three pro:foo") - self.assertRegex(err, self.STATUS.format("foo", "0%", - "3 of 3 tasks")) + self.assertRegex(err, self.STATUS.format("foo", "0%", "3 of 3 tasks")) code, out, err = self.t("add four pro:foo") - self.assertRegex(err, self.STATUS.format("foo", "0%", - "4 of 4 tasks")) + self.assertRegex(err, self.STATUS.format("foo", "0%", "4 of 4 tasks")) code, out, err = self.t("1 done") - self.assertRegex(err, self.STATUS.format("foo", "25%", - "3 of 4 tasks")) + self.assertRegex(err, self.STATUS.format("foo", "25%", "3 of 4 tasks")) code, out, err = self.t("2 delete", input="y\n") - self.assertRegex(err, self.STATUS.format("foo", "33%", - "2 of 3 tasks")) + self.assertRegex(err, self.STATUS.format("foo", "33%", "2 of 3 tasks")) code, out, err = self.t("3 modify pro:bar") - self.assertRegex(err, self.STATUS.format("foo", "50%", - "1 of 2 tasks")) - self.assertRegex(err, self.STATUS.format("bar", "0%", - "1 task")) + self.assertRegex(err, self.STATUS.format("foo", "50%", "1 of 2 tasks")) + self.assertRegex(err, self.STATUS.format("bar", "0%", "1 task")) def test_project_spaces(self): """projects with spaces are handled correctly""" self.t("add hello pro:bob") code, out, err = self.t('1 mod pro:"foo bar"') - self.assertRegex(err, self.STATUS.format("foo bar", "0%", - "1 task")) + self.assertRegex(err, self.STATUS.format("foo bar", "0%", "1 task")) # Ensure filtering for project with spaces works code, out, err = self.t('pro:"foo bar" count') - self.assertEqual(out.strip(), '1') + self.assertEqual(out.strip(), "1") def test_project_spaces(self): """TW #2386: Filter for project:someday""" @@ -106,8 +99,8 @@ class TestProjects(TestCase): self.t("add hello pro:someday") # Ensure filtering for project with numeric date works - code, out, err = self.t('pro:someday count') - self.assertEqual(out.strip(), '1') + code, out, err = self.t("pro:someday count") + self.assertEqual(out.strip(), "1") def add_tasks(self): self.t("add testing project:existingParent") @@ -121,7 +114,7 @@ class TestProjects(TestCase): order = ( ".myProject ", ".myProject. ", - "abstractParent", # No space at EOL because this line in the summary ends here. + "abstractParent", # No space at EOL because this line in the summary ends here. " kid ", "existingParent ", " child ", @@ -137,8 +130,10 @@ class TestProjects(TestCase): self.assertTrue( lines[pos].startswith(proj), - msg=("Project '{0}' is not in line #{1} or has an unexpected " - "indentation.{2}".format(proj, pos, out)) + msg=( + "Project '{0}' is not in line #{1} or has an unexpected " + "indentation.{2}".format(proj, pos, out) + ), ) def test_project_indentation(self): @@ -322,7 +317,7 @@ class TestBug906(TestCase): def test_project_hierarchy_filter(self): """906: Test project hierarchy filters - Bug 906 + Bug 906 """ self.t("add zero") self.t("add one pro:a.b") @@ -356,7 +351,7 @@ class TestBug856(TestCase): def test_project_hierarchy_filter(self): """856: Test project.none: works - Bug 856: "task list project.none:" does not work. + Bug 856: "task list project.none:" does not work. """ self.t("add assigned project:X") self.t("add floating") @@ -365,7 +360,7 @@ class TestBug856(TestCase): self.assertIn("floating", out) self.assertNotIn("assigned", out) - code, out, err = self.t("project:\'\' ls") + code, out, err = self.t("project:'' ls") self.assertIn("floating", out) self.assertNotIn("assigned", out) @@ -381,7 +376,7 @@ class TestBug1511(TestCase): def test_project_hierarchy_filter(self): """1511: Test project:one-two can be added and queried - Bug 1511: Project titles not properly parsed if they contain hyphens + Bug 1511: Project titles not properly parsed if they contain hyphens """ self.t("add zero") self.t("add one project:two-three") @@ -397,7 +392,7 @@ class TestBug1455(TestCase): def test_project_hierarchy_filter(self): """1455: Test project:school) - Bug 1455: Filter parser does not properly handle parentheses in attributes + Bug 1455: Filter parser does not properly handle parentheses in attributes """ self.t("add zero") self.t("add one project:two)") @@ -415,16 +410,16 @@ class TestBug899(TestCase): def test_log_project(self): """899: Verify task log behaves correctly when logging into a project""" code, out, err = self.t("add one pro:A") - self.assertRegex(err, " 0% complete \(1 task ") + self.assertRegex(err, r" 0% complete \(1 task ") code, out, err = self.t("add two pro:A") - self.assertRegex(err, " 0% complete \(2 of 2 ") + self.assertRegex(err, r" 0% complete \(2 of 2 ") code, out, err = self.t("1 done") - self.assertRegex(err, " 50% complete \(1 of 2 ") + self.assertRegex(err, r" 50% complete \(1 of 2 ") code, out, err = self.t("log three pro:A") - self.assertRegex(err, " 66% complete \(1 of 3 ") + self.assertRegex(err, r" 66% complete \(1 of 3 ") class TestBug1267(TestCase): @@ -432,16 +427,14 @@ class TestBug1267(TestCase): self.t = Task() def test_add_task_no_project_with_default(self): - """1267: Add a task without a project using direct rc change - """ + """1267: Add a task without a project using direct rc change""" project = "MakePudding" self.t("rc.default.project={0} add proj: 'Add cream'".format(project)) code, out, err = self.t("ls") self.assertNotIn(project, out) def test_add_task_no_project_with_default_rcfile(self): - """1267: Add a task without a project writing to rc file - """ + """1267: Add a task without a project writing to rc file""" project = "MakePudding" self.t.config("default.project", project) self.t("add proj: 'Add cream'") @@ -515,9 +508,7 @@ class TestBug1904(TestCase): self.t("add pro:a.b test2") def validate_order(self, out): - order = ("a", - " b", - "a-b") + order = ("a", " b", "a-b") lines = out.splitlines(True) # position where project names start on the lines list @@ -528,8 +519,10 @@ class TestBug1904(TestCase): self.assertTrue( lines[pos].startswith(proj), - msg=("Project '{0}' is not in line #{1} or has an unexpected " - "indentation.{2}".format(proj, pos, out)) + msg=( + "Project '{0}' is not in line #{1} or has an unexpected " + "indentation.{2}".format(proj, pos, out) + ), ) def test_project_eval(self): @@ -541,6 +534,7 @@ class TestBug1904(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/purge.test.py b/test/purge.test.py new file mode 100755 index 000000000..2518a8103 --- /dev/null +++ b/test/purge.test.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2006 - 2024, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import unittest +import time + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase +from basetest.utils import mkstemp + + +class TestAutoPurge(TestCase): + def setUp(self): + self.t = Task() + # Set up local sync within the TASKDATA directory, so that it will be + # deleted properly. + self.t.config("sync.local.server_dir", self.t.datadir) + + def exists(self, uuid): + code, out, err = self.t(f"_get {uuid}.status") + return out.strip() != "" + + def test_auto_purge(self): + """Only tasks that are deleted and have a modification in the past are purged.""" + yesterday = int(time.time()) - 3600 * 24 + last_year = int(time.time()) - 265 * 3600 * 24 + old_pending = "a1111111-a111-a111-a111-a11111111111" + old_completed = "a2222222-a222-a222-a222-a22222222222" + new_deleted = "a3333333-a333-a333-a333-a33333333333" + old_deleted = "a4444444-a444-a444-a444-a44444444444" + task_data = f"""[ + {{"uuid":"{old_pending}","status":"pending","modified":"{last_year}","description":"x"}}, + {{"uuid":"{old_completed}","status":"completed","modified":"{last_year}","description":"x"}}, + {{"uuid":"{new_deleted}","status":"deleted","modified":"{yesterday}","description":"x"}}, + {{"uuid":"{old_deleted}","status":"deleted","modified":"{last_year}","description":"x"}} +] +""" + code, out, err = self.t("import -", input=task_data) + self.assertIn("Imported 4 tasks", err) + + # By default, purge does not occur. + code, out, err = self.t("sync") + self.assertTrue(self.exists(old_pending)) + self.assertTrue(self.exists(old_completed)) + self.assertTrue(self.exists(new_deleted)) + self.assertTrue(self.exists(old_deleted)) + + # Configure purge on sync. The old_deleted task + # should be removed. + self.t.config("purge.on-sync", "1") + code, out, err = self.t("sync") + self.assertTrue(self.exists(old_pending)) + self.assertTrue(self.exists(old_completed)) + self.assertTrue(self.exists(new_deleted)) + self.assertFalse(self.exists(old_deleted)) + + +class TestDelete(TestCase): + def setUp(self): + self.t = Task() + + def test_add_delete_purge(self): + """Verify that add/delete/purge successfully purges a task""" + self.t("add one") + uuid = self.t("_get 1.uuid")[1].strip() + + code, out, err = self.t("1 delete", input="y\n") + self.assertIn("Deleted 1 task.", out) + + code, out, err = self.t(uuid + " purge", input="y\n") + self.assertIn("Purged 1 task.", out) + + code, out, err = self.t("uuids") + self.assertNotIn(uuid, out) + + def test_purge_remove_deps(self): + """Purge command removes broken dependency references""" + self.t("add one") + self.t("add two dep:1") + uuid = self.t("_get 1.uuid")[1].strip() + + code, out, err = self.t("1 delete", input="y\n") + self.assertIn("Deleted 1 task.", out) + + code, out, err = self.t(uuid + " purge", input="y\n") + self.assertIn("Purged 1 task.", out) + + code, out, err = self.t("uuids") + self.assertNotIn(uuid, out) + + dependencies = self.t("_get 1.depends")[1].strip() + self.assertNotIn(uuid, dependencies) + + def test_purge_children(self): + """Purge command indirectly purges child tasks""" + self.t("add one recur:daily due:yesterday") + uuid = self.t("_get 1.uuid")[1].strip() + + # A dummy call to report, so that recurrence tasks get generated + self.t("list") + + code, out, err = self.t("1 delete", input="y\ny\n") + self.assertIn("Deleted 4 tasks.", out) + + code, out, err = self.t(uuid + " purge", input="y\ny\n") + self.assertIn("Purged 4 tasks.", out) + + code, out, err = self.t("uuids") + self.assertEqual("\n", out) + + def test_purge_children_fail_pending(self): + """Purge aborts if task has pending children""" + self.t("add one recur:daily due:yesterday") + uuid = self.t("_get 1.uuid")[1].strip() + + # A dummy call to report, so that recurrence tasks get generated + self.t("list") + + code, out, err = self.t("1 delete", input="y\nn\n") + self.assertIn("Deleted 1 task.", out) + + code, out, err = self.t.runError(uuid + " purge", input="y\n") + # The id of the problematic task is not deterministic, as there are + # three child tasks. + self.assertIn("child task", err) + self.assertIn("must be deleted before", err) + + # Check that nothing was purged + code, out, err = self.t("count") + self.assertEqual("4\n", out) + + def test_purge_children_fail_confirm(self): + """Purge aborts if user does not agree with it affecting child tasks""" + self.t("add one recur:daily due:yesterday") + uuid = self.t("_get 1.uuid")[1].strip() + + # A dummy call to report, so that recurrence tasks get generated + self.t("list") + + code, out, err = self.t("1 delete", input="y\ny\n") + self.assertIn("Deleted 4 tasks.", out) + + # Do not agree with purging of the child tasks + code, out, err = self.t.runError(uuid + " purge", input="y\nn\n") + self.assertIn("Purge operation aborted.", err) + + # Check that nothing was purged + code, out, err = self.t("count") + self.assertEqual("4\n", out) + + def test_purge_children(self): + """Purge command removes dependencies on indirectly purged tasks""" + self.t("add one recur:daily due:yesterday") + uuid = self.t("_get 1.uuid")[1].strip() + + # A dummy call to report, so that recurrence tasks get generated + self.t("list") + self.t("add two dep:4") + + # Check that the dependency is present + dependencies = self.t("_get 5.depends")[1].strip() + self.assertNotEqual("", dependencies) + + code, out, err = self.t("1 delete", input="y\ny\n") + self.assertIn("Deleted 4 tasks.", out) + + code, out, err = self.t(uuid + " purge", input="y\ny\n") + self.assertIn("Purged 4 tasks.", out) + + # Make sure we are dealing with the intended task + description = self.t("_get 1.description")[1].strip() + self.assertEqual("two", description) + + # Check that the dependency was removed + dependencies = self.t("_get 1.depends")[1].strip() + self.assertEqual("", dependencies) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/quotes.t b/test/quotes.test.py similarity index 80% rename from test/quotes.t rename to test/quotes.test.py index 9f962b595..56b5a5518 100755 --- a/test/quotes.t +++ b/test/quotes.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -61,27 +61,29 @@ class TestBug268(TestCase): """escaped backslashes do not work with 'modify'""" self.t("add a b or c") - self.t('1 modify "/a b/a\/b/"') + self.t(r'1 modify "/a b/a\/b/"') code, out, err = self.t("1 info") self.assertIn("a/b or c", out) -class TestBug880(TestCase): +class TestBug3858(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() def test_backslash_at_eol(self): - """880: Backslash at end of description/annotation causes problems""" + """880: Backslashes at end of description/annotation are handled correctly""" self.t(r"add one\\") code, out, err = self.t("_get 1.description") self.assertEqual("one\\\n", out) - self.t(r"1 annotate 'two\\'") + self.t(r"1 annotate 'two\'") + self.t(r"1 annotate 'three\\'") code, out, err = self.t("info rc.verbose:nothing") self.assertIn("one\\\n", out) self.assertIn("two\\\n", out) + self.assertIn("three\\\\\n", out) class TestBug1436(TestCase): @@ -125,34 +127,35 @@ class TestBug1436(TestCase): def test_backslashes(self): """1436: Prove to the reader that backslashes are eaten twice (which means - \\ --> \) once by Python, and once more by some mystery process - launch thing. + two backslashes to one) once by Python, and once more by some mystery process + launch thing. - This problem is entirely testing artifact, and not Taskwarrior. + This problem is entirely testing artifact, and not Taskwarrior. """ self.echo = Task(taskw=utils.binary_location("echo", USE_PATH=True)) # One level of backshashes gets eaten by bash # Verify with: $ echo xxx \\\\yyy zzz - code, out, err = self.echo(r"xxx \\\\yyy zzz") # Shows as 'xxx \\yyy zzz' - code, out, err = self.echo(r"xxx \\yyy zzz") # Shows as 'xxx \yyy zzz' - code, out, err = self.echo(r"xxx \yyy zzz") # Shows as 'xxx yyy zzz' + code, out, err = self.echo(r"xxx \\\\yyy zzz") # Shows as 'xxx \\yyy zzz' + code, out, err = self.echo(r"xxx \\yyy zzz") # Shows as 'xxx \yyy zzz' + code, out, err = self.echo(r"xxx \yyy zzz") # Shows as 'xxx yyy zzz' # If single quotes are used, the backslashes are not eaten # Verify with: $ echo xxx '\\\\yyy' zzz - code, out, err = self.echo(r"xxx '\\\\yyy' zzz") # Shows as 'xxx \\\\yyy zzz' - code, out, err = self.echo(r"xxx '\\yyy' zzz") # Shows as 'xxx \\yyy zzz' - code, out, err = self.echo(r"xxx '\yyy' zzz") # Shows as 'xxx \yyy zzz' + code, out, err = self.echo(r"xxx '\\\\yyy' zzz") # Shows as 'xxx \\\\yyy zzz' + code, out, err = self.echo(r"xxx '\\yyy' zzz") # Shows as 'xxx \\yyy zzz' + code, out, err = self.echo(r"xxx '\yyy' zzz") # Shows as 'xxx \yyy zzz' # If double quotes are used, the backslashes are eaten # Verify with: $ echo xxx "\\\\yyy" zzz - code, out, err = self.echo(r'xxx "\\\\yyy" zzz') # Shows as 'xxx \\\\yyy zzz' - code, out, err = self.echo(r'xxx "\\yyy" zzz') # Shows as 'xxx \\yyy zzz' - code, out, err = self.echo(r'xxx "\yyy" zzz') # Shows as 'xxx \yyy zzz' + code, out, err = self.echo(r'xxx "\\\\yyy" zzz') # Shows as 'xxx \\\\yyy zzz' + code, out, err = self.echo(r'xxx "\\yyy" zzz') # Shows as 'xxx \\yyy zzz' + code, out, err = self.echo(r'xxx "\yyy" zzz') # Shows as 'xxx \yyy zzz' if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/rc.override.t b/test/rc.override.test.py similarity index 97% rename from test/rc.override.t rename to test/rc.override.test.py index fc11e0277..ce83dacd2 100755 --- a/test/rc.override.t +++ b/test/rc.override.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,7 +40,7 @@ class TestOverride(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() - self.t.config("regex", "0") + self.t.config("regex", "0") self.t.config("verbose", "nothing") def test_override(self): @@ -72,6 +72,7 @@ class TestRCSegfault(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/read-only.test.py b/test/read-only.test.py new file mode 100755 index 000000000..9385a1b5c --- /dev/null +++ b/test/read-only.test.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import platform +import time +import unittest + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestReadOnly(TestCase): + def setUp(self): + self.t = Task() + self.t("add foo") + + # set the mtime of the taskdb to an hour ago, so we can see any changes + self.taskdb = self.t.datadir + "/taskchampion.sqlite3" + os.utime(self.taskdb, (time.time() - 3600,) * 2) + + def assertNotModified(self): + self.assertLess(os.stat(self.taskdb).st_mtime, time.time() - 1800) + + def assertModified(self): + self.assertGreater(os.stat(self.taskdb).st_mtime, time.time() - 1800) + + def test_read_only_command(self): + code, out, err = self.t("reports") + self.assertNotModified() + + def test_report(self): + code, out, err = self.t("list") + self.assertModified() + + def test_burndown(self): + code, out, err = self.t("burndown") + self.assertModified() + + def test_report_gc_0(self): + self.t.config("gc", "0") + code, out, err = self.t("list") + self.assertNotModified() + + def test_burndown_gc_0(self): + self.t.config("gc", "0") + code, out, err = self.t("burndown") + self.assertNotModified() + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/recurrence.t b/test/recurrence.test.py similarity index 80% rename from test/recurrence.t rename to test/recurrence.test.py index fa6268431..155eb9d1f 100755 --- a/test/recurrence.t +++ b/test/recurrence.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -31,6 +30,7 @@ import os import re import time import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,12 +42,12 @@ class TestRecurrenceSorting(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("report.asc.columns", "id,recur,description") - cls.t.config("report.asc.sort", "recur+") - cls.t.config("report.asc.filter", "status:pending") + cls.t.config("report.asc.columns", "id,recur,description") + cls.t.config("report.asc.sort", "recur+") + cls.t.config("report.asc.filter", "status:pending") cls.t.config("report.desc.columns", "id,recur,description") - cls.t.config("report.desc.sort", "recur-") - cls.t.config("report.desc.filter", "status:pending") + cls.t.config("report.desc.sort", "recur-") + cls.t.config("report.desc.filter", "status:pending") cls.t("add one due:tomorrow recur:daily") cls.t("add two due:tomorrow recur:weekly") @@ -59,12 +59,12 @@ class TestRecurrenceSorting(TestCase): def test_sort_ascending(self): """Verify sorting by 'recur+' is correct""" code, out, err = self.t("asc rc.verbose:nothing") - self.assertRegex(out, "4\s+P1D\s+one\s+6\s+P3D\s+three\s+5\s+P7D\s+two") + self.assertRegex(out, r"4\s+P1D\s+one\s+6\s+P3D\s+three\s+5\s+P7D\s+two") def test_sort_descending(self): """Verify sorting by 'recur-' is correct""" code, out, err = self.t("desc rc.verbose:nothing") - self.assertRegex(out, "5\s+P7D\s+two\s+6\s+P3D\s+three\s+4\s+P1D\s+one") + self.assertRegex(out, r"5\s+P7D\s+two\s+6\s+P3D\s+three\s+4\s+P1D\s+one") class TestRecurrenceDisabled(TestCase): @@ -179,9 +179,9 @@ class TestRecurrenceTasks(TestCase): # 3 complex # 4 complex code, out, err = self.t("minimal rc.verbose:nothing") - self.assertRegex(out, "1\s+simple") - self.assertRegex(out, "3\s+complex") - self.assertRegex(out, "4\s+complex") + self.assertRegex(out, r"1\s+simple") + self.assertRegex(out, r"3\s+complex") + self.assertRegex(out, r"4\s+complex") # Modify a child task and do not propagate the change. self.t("3 modify complex2", input="n\n") @@ -206,10 +206,6 @@ class TestRecurrenceTasks(TestCase): code, out, err = self.t("3 delete", input="y\n") self.assertIn("Deleted 1 task.", out) - # Check for duplicate UUIDs. - code, out, err = self.t("diag") - self.assertIn("No duplicates found", out) - class TestBug972(TestCase): def setUp(self): @@ -218,7 +214,7 @@ class TestBug972(TestCase): def test_interpretation_of_seven(self): """Bug 972: A recurrence period of "7" is interpreted as "7s", not "7d" - as intended. + as intended. """ code, out, err = self.t.runError("add one due:now recur:2") self.assertIn("The duration value '2' is not supported.", err) @@ -232,7 +228,7 @@ class TestDeletionRecurrence(TestCase): def test_delete_parent(self): """Delete a parent with child tasks""" self.t("add one due:eom recur:daily") - self.t("list") # GC/handleRecurrence + self.t("list") # GC/handleRecurrence code, out, err = self.t("1 delete", input="y\ny\n") self.assertIn("Deleted 2 tasks.", out) @@ -243,7 +239,7 @@ class TestDeletionRecurrence(TestCase): """Delete a child with sibling tasks""" self.t("add one due:eom recur:daily") self.t("list rc.recurrence.limit:5") - code, out, err = self.t("list rc.verbose:nothing") # GC/handleRecurrence + code, out, err = self.t("list rc.verbose:nothing") # GC/handleRecurrence self.assertEqual(out.count("one"), 5) code, out, err = self.t("2 delete", input="y\ny\n") @@ -258,7 +254,7 @@ class TestAppendPrependRecurrence(TestCase): def test_append_propagate(self): """Append and propagate""" self.t("add one due:eom recur:daily") - self.t("list rc.recurrence.limit:2") # GC/handleRecurrence + self.t("list rc.recurrence.limit:2") # GC/handleRecurrence code, out, err = self.t("2 append APP", input="y\n") self.assertIn("Appended 2 tasks.", out) @@ -266,7 +262,7 @@ class TestAppendPrependRecurrence(TestCase): def test_prepend_propagate(self): """Prepend and propagate""" self.t("add one due:eom recur:daily") - self.t("list rc.recurrence.limit:2") # GC/handleRecurrence + self.t("list rc.recurrence.limit:2") # GC/handleRecurrence code, out, err = self.t("2 prepend PRE", input="y\n") self.assertIn("Prepended 2 tasks.", out) @@ -365,11 +361,12 @@ class TestBug955(TestCase): self.assertIn("Deleted 2 tasks", out) code, out, err = self.t.runError("all status:recurring") - self.assertIn("No matches", err) + self.assertIn("No matches", err) code, out, err = self.t.runError("ls") self.assertIn("No matches", err) + class TestUpgradeToRecurring(TestCase): def setUp(self): """Executed before each test in the class""" @@ -389,6 +386,7 @@ class TestUpgradeToRecurring(TestCase): code, out, err = self.t.runError("1 modify recur:weekly") self.assertIn("You cannot specify a recurring task without a due date.", err) + class TestRecurrenceNotification(TestCase): def setUp(self): """Executed before each test in the class""" @@ -405,6 +403,7 @@ class TestRecurrenceNotification(TestCase): code, out, err = self.t("list") self.assertNotIn("Creating recurring task instance 'foo'", err) + class BaseTestBug360(TestCase): def setUp(self): """Executed before each test in the class""" @@ -413,10 +412,10 @@ class BaseTestBug360(TestCase): # This command forces a handleRecurrence() call to generate synthetic tasks. self.t("ls") + class TestBug360RemovalError(BaseTestBug360): def test_modify_recursive_project(self): - """360: Modifying a recursive task by adding project: also modifies parent - """ + """360: Modifying a recursive task by adding project: also modifies parent""" code, out, err = self.t("1 modify project:bar", input="y\n") expected = "Modified 2 tasks." @@ -425,8 +424,7 @@ class TestBug360RemovalError(BaseTestBug360): self.assertNotIn(expected, err) def test_cannot_remove_recurrence(self): - """360: Cannot remove recurrence from recurring task - """ + """360: Cannot remove recurrence from recurring task""" # TODO Removing recur: from a recurring task should also remove imask # and parent. @@ -438,8 +436,7 @@ class TestBug360RemovalError(BaseTestBug360): self.assertIn(expected, err) def test_cannot_remove_due_date(self): - """360: Cannot remove due date from recurring task - """ + """360: Cannot remove due date from recurring task""" # TODO Removing due: from a recurring task should also remove recur, # imask and parent code, out, err = self.t.runError("2 modify due:") @@ -476,10 +473,6 @@ class TestBug360AllowedChanges(BaseTestBug360): expected = "You cannot remove the due date from a recurring task." self.assertNotIn(expected, err) - # Make sure no duplicate tasks were created - code, out, err = self.t.diag() - expected = "No duplicates found" - self.assertIn(expected, out) class TestBug649(TestCase): def setUp(self): @@ -493,6 +486,7 @@ class TestBug649(TestCase): self.assertIn("is neither pending nor waiting", out) self.assertNotIn("Completed 1", out) + class TestBugC001(TestCase): def setUp(self): """Executed before each test in the class""" @@ -503,6 +497,7 @@ class TestBugC001(TestCase): code, out, err = self.t("add one due:tomorrow recur:daily") code, out, err = self.t("add two due:tomorrow recur:daily") + class TestBug839(TestCase): def setUp(self): """Executed before each test in the class""" @@ -512,7 +507,10 @@ class TestBug839(TestCase): """839: Verify that importing a legacy recurrence value is ok""" # use a recent timestamp to avoid slowly iterating over large number of tasks justnow = int(time.time()) - 120 - json = '{"description":"one","due":"%s","recur":"1m","status":"recurring","uuid":"ebeeab00-ccf8-464b-8b58-f7f2d606edfb"}' % justnow + json = ( + '{"description":"one","due":"%s","recur":"1m","status":"recurring","uuid":"ebeeab00-ccf8-464b-8b58-f7f2d606edfb"}' + % justnow + ) self.t("import -", input=json) code, out, err = self.t("list") @@ -530,79 +528,75 @@ class TestPeriod(TestCase): self.t = Task() def test_recurrence_periods(self): - """Verify recurrence period special-case support + """Verify recurrence period special-case support - Date getNextRecurrence (Date& current, std::string& period) + Date getNextRecurrence (Date& current, std::string& period) - Confirmed: - getNextRecurrence convertDuration - ----------------- --------------- - daily - day - weekly - sennight - biweekly - fortnight - monthly monthly - quarterly quarterly - semiannual semiannual - bimonthly bimonthly - biannual biannual - biyearly biyearly - annual - yearly - *m *m - *q *q - *d - *w - *y - """ + Confirmed: + getNextRecurrence convertDuration + ----------------- --------------- + daily + day + weekly + sennight + biweekly + fortnight + monthly monthly + quarterly quarterly + semiannual semiannual + bimonthly bimonthly + biannual biannual + biyearly biyearly + annual + yearly + *m *m + *q *q + *d + *w + *y + """ - self.t("add daily due:tomorrow recur:daily") - self.t("add 1day due:tomorrow recur:1day") - self.t("add weekly due:tomorrow recur:weekly") - self.t("add 1sennight due:tomorrow recur:1sennight") - self.t("add biweekly due:tomorrow recur:biweekly") - self.t("add fortnight due:tomorrow recur:fortnight") - self.t("add monthly due:tomorrow recur:monthly") - self.t("add quarterly due:tomorrow recur:quarterly") - self.t("add semiannual due:tomorrow recur:semiannual") - self.t("add bimonthly due:tomorrow recur:bimonthly") - self.t("add biannual due:tomorrow recur:biannual") - self.t("add biyearly due:tomorrow recur:biyearly") - self.t("add annual due:tomorrow recur:annual") - self.t("add yearly due:tomorrow recur:yearly") - self.t("add 2d due:tomorrow recur:2d") - self.t("add 2w due:tomorrow recur:2w") - self.t("add 2mo due:tomorrow recur:2mo") - self.t("add 2q due:tomorrow recur:2q") - self.t("add 2y due:tomorrow recur:2y") + self.t("add daily due:tomorrow recur:daily") + self.t("add 1day due:tomorrow recur:1day") + self.t("add weekly due:tomorrow recur:weekly") + self.t("add 1sennight due:tomorrow recur:1sennight") + self.t("add biweekly due:tomorrow recur:biweekly") + self.t("add fortnight due:tomorrow recur:fortnight") + self.t("add monthly due:tomorrow recur:monthly") + self.t("add quarterly due:tomorrow recur:quarterly") + self.t("add semiannual due:tomorrow recur:semiannual") + self.t("add bimonthly due:tomorrow recur:bimonthly") + self.t("add biannual due:tomorrow recur:biannual") + self.t("add biyearly due:tomorrow recur:biyearly") + self.t("add annual due:tomorrow recur:annual") + self.t("add yearly due:tomorrow recur:yearly") + self.t("add 2d due:tomorrow recur:2d") + self.t("add 2w due:tomorrow recur:2w") + self.t("add 2mo due:tomorrow recur:2mo") + self.t("add 2q due:tomorrow recur:2q") + self.t("add 2y due:tomorrow recur:2y") - # Verify that the recurring task instances were created. One of each. - code, out, err = self.t("list") - self.assertIn(" daily ", out); - self.assertIn(" 1day ", out); - self.assertIn(" weekly ", out); - self.assertIn(" 1sennight ", out); - self.assertIn(" biweekly ", out); - self.assertIn(" fortnight ", out); - self.assertIn(" monthly ", out); - self.assertIn(" quarterly ", out); - self.assertIn(" semiannual ", out); - self.assertIn(" bimonthly ", out); - self.assertIn(" biannual ", out); - self.assertIn(" biyearly ", out); - self.assertIn(" annual ", out); - self.assertIn(" yearly ", out); - self.assertIn(" 2d ", out); - self.assertIn(" 2w ", out); - self.assertIn(" 2mo ", out); - self.assertIn(" 2q ", out); - self.assertIn(" 2y ", out); - - # Duplicate check - code, out, err = self.t("diag") - self.assertIn("No duplicates found", out) + # Verify that the recurring task instances were created. One of each. + code, out, err = self.t("list") + self.assertIn(" daily ", out) + self.assertIn(" 1day ", out) + self.assertIn(" weekly ", out) + self.assertIn(" 1sennight ", out) + self.assertIn(" biweekly ", out) + self.assertIn(" fortnight ", out) + self.assertIn(" monthly ", out) + self.assertIn(" quarterly ", out) + self.assertIn(" semiannual ", out) + self.assertIn(" bimonthly ", out) + self.assertIn(" biannual ", out) + self.assertIn(" biyearly ", out) + self.assertIn(" annual ", out) + self.assertIn(" yearly ", out) + self.assertIn(" 2d ", out) + self.assertIn(" 2w ", out) + self.assertIn(" 2mo ", out) + self.assertIn(" 2q ", out) + self.assertIn(" 2y ", out) class TestBugAnnual(TestCase): @@ -612,11 +606,11 @@ class TestBugAnnual(TestCase): def test_annual_creep(self): """Verify 'annual' recurring tasks don't creep""" - self.t.config("dateformat", "YMD") - self.t.config("report.annual.labels", "ID,Due") + self.t.config("dateformat", "YMD") + self.t.config("report.annual.labels", "ID,Due") self.t.config("report.annual.columns", "id,due") - self.t.config("report.annual.filter", "status:pending") - self.t.config("report.annual.sort", "due+") + self.t.config("report.annual.filter", "status:pending") + self.t.config("report.annual.sort", "due+") # If a task is added with a due date ten years ago, with an annual recurrence, # then the synthetic tasks in between then and now have a due date that creeps. @@ -663,6 +657,7 @@ class TestBugAnnual(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/reports.t b/test/reports.test.py similarity index 98% rename from test/reports.t rename to test/reports.test.py index 051421c05..b1d763ae7 100755 --- a/test/reports.t +++ b/test/reports.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -55,6 +55,7 @@ class TestReportCommand(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/run_all b/test/run_all deleted file mode 100755 index 8668d685f..000000000 --- a/test/run_all +++ /dev/null @@ -1,239 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from __future__ import print_function -import os -import sys -import glob -import argparse -import logging -import time -from multiprocessing import cpu_count -from threading import Thread -from subprocess import call, Popen, PIPE - -if sys.version_info > (3,): - import codecs - -try: - # python 2 - from Queue import Queue, Empty -except ImportError: - # python 3 - from queue import Queue, Empty - -TIMEOUT = .2 - - -def run_test(testqueue, outqueue, threadname): - start = time.time() - while True: - try: - test = testqueue.get(block=True, timeout=TIMEOUT) - except Empty: - break - - log.info("Running test %s", test) - - try: - p = Popen(os.path.abspath(test), stdout=PIPE, stderr=PIPE, - env=os.environ) - out, err = p.communicate() - except Exception as e: - log.exception(e) - # Premature end - break - - if sys.version_info > (3,): - out, err = out.decode('utf-8'), err.decode('utf-8') - - output = ("# {0}\n".format(os.path.basename(test)), out, err) - log.debug("Collected output %s", output) - outqueue.put(output) - - testqueue.task_done() - - log.warning("Finished %s thread after %s seconds", - threadname, round(time.time() - start, 3)) - - -class TestRunner(object): - def __init__(self): - self.threads = [] - if sys.version_info > (3,): - self.tap = open(cmd_args.tapfile, 'w', errors='ignore') - else: - self.tap = open(cmd_args.tapfile, 'w') - - self._parallelq = Queue() - self._serialq = Queue() - self._outputq = Queue() - - def _find_tests(self): - for test in glob.glob("*.t") + glob.glob("*.t.exe"): - if os.access(test, os.X_OK): - # Executables only - if self._is_parallelizable(test): - log.debug("Treating as parallel: %s", test) - self._parallelq.put(test) - else: - log.debug("Treating as serial: %s", test) - self._serialq.put(test) - else: - log.debug("Ignored test %s as it is not executable", test) - - log.info("Parallel tests: %s", self._parallelq.qsize()) - log.info("Serial tests: %s", self._serialq.qsize()) - - def _prepare_threads(self): - # Serial thread - self.threads.append( - Thread(target=run_test, args=(self._serialq, self._outputq, "Serial")) - ) - # Parallel threads - self.threads.extend([ - Thread(target=run_test, args=(self._parallelq, self._outputq, "Parallel")) - for i in range(cpu_count()) - ]) - log.info("Spawned %s threads to run tests", len(self.threads)) - - def _start_threads(self): - for thread in self.threads: - # Threads die when main thread dies - log.debug("Starting thread %s", thread) - thread.daemon = True - thread.start() - - def _print_timestamp_to_tap(self): - now = time.time() - timestamp = "# {0} ==> {1}\n".format( - now, - time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(now)), - ) - - log.debug("Adding timestamp %s to TAP file", timestamp) - self.tap.write(timestamp) - - def _is_parallelizable(self, test): - if cmd_args.serial: - return False - - # This is a pretty weird way to do it, and not realiable. - # We are dealing with some binary tests though. - with open(test, 'rb') as fh: - header = fh.read(100).split(b"\n") - if len(header) >= 2 and \ - ((b"/usr/bin/env python3" in header[0]) or \ - (header[1][-14:] == b"bash_tap_tw.sh")): - return True - else: - return False - - def _get_remaining_tests(self): - return self._parallelq.qsize() + self._serialq.qsize() - - def is_running(self): - for thread in self.threads: - if thread.is_alive(): - return True - - return False - - def start(self): - self._find_tests() - self._prepare_threads() - - self._print_timestamp_to_tap() - - finished = 0 - total = self._get_remaining_tests() - - self._start_threads() - - while self.is_running() or not self._outputq.empty(): - try: - outputs = self._outputq.get(block=True, timeout=TIMEOUT) - except Empty: - continue - - log.debug("Outputting to TAP: %s", outputs) - - for output in outputs: - self.tap.write(output) - - if cmd_args.verbose: - sys.stdout.write(output) - - self._outputq.task_done() - finished += 1 - - log.warning("Finished %s out of %s tests", finished, total) - - self._print_timestamp_to_tap() - - if not self._parallelq.empty() or not self._serialq.empty(): - raise RuntimeError( - "Something went wrong, not all tests were ran. {0} " - "remaining.".format(self._get_remaining_tests())) - - def show_report(self): - self.tap.flush() - sys.stdout.flush() - sys.stderr.flush() - - log.debug("Calling 'problems --summary' for report") - return call([os.path.abspath("problems"), "--summary", cmd_args.tapfile]) - - -def parse_args(): - parser = argparse.ArgumentParser(description="Run Taskwarrior tests") - parser.add_argument('--verbose', '-v', action="store_true", - help="Also send TAP output to stdout") - parser.add_argument('--logging', '-l', action="count", - default=0, - help="Logging level. -lll is the highest level") - parser.add_argument('--serial', action="store_true", - help="Do not run tests in parallel") - parser.add_argument('--tapfile', default="all.log", - help="File to use for TAP output") - return parser.parse_args() - - -def main(): - if sys.version_info > (3,): - sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach()) - - runner = TestRunner() - runner.start() - - # Propagate the return code - return runner.show_report() - - -if __name__ == "__main__": - cmd_args = parse_args() - - if cmd_args.logging == 1: - level = logging.WARN - elif cmd_args.logging == 2: - level = logging.INFO - elif cmd_args.logging >= 3: - level = logging.DEBUG - else: - level = logging.ERROR - - logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(message)s", - level=level, - ) - log = logging.getLogger(__name__) - - log.debug("Parsed commandline arguments: %s", cmd_args) - - try: - sys.exit(main()) - except Exception as e: - log.exception(e) - sys.exit(1) - -# vim: ai sts=4 et sw=4 diff --git a/test/scripts/test_macos.sh b/test/scripts/test_macos.sh index cc86ece07..b471a7e1c 100755 --- a/test/scripts/test_macos.sh +++ b/test/scripts/test_macos.sh @@ -14,4 +14,4 @@ pushd test make ./run_all -v cat all.log | grep 'not ok' -./problems \ No newline at end of file +./problems diff --git a/test/search.t b/test/search.test.py similarity index 78% rename from test/search.t rename to test/search.test.py index 08491e6b6..6be972f86 100755 --- a/test/search.t +++ b/test/search.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import platform + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -48,7 +48,7 @@ class TestSearch(TestCase): def test_plain_arg(self): """Verify plain args are interpreted as search terms - tw-1635: Running "task anystringatall" does not filter anything + tw-1635: Running "task anystringatall" does not filter anything """ code, out, err = self.t("one list") self.assertIn("one", out) @@ -66,6 +66,7 @@ class TestSearch(TestCase): self.assertIn("one", out) self.assertNotIn("two", out) + class Test1418(TestCase): def setUp(self): self.t = Task() @@ -141,6 +142,7 @@ class Test1418(TestCase): code, out, err = self.t("/foo\\+/") self.assertIn(description, out) + class TestBug1472(TestCase): @classmethod def setUpClass(cls): @@ -181,67 +183,74 @@ class TestBug1472(TestCase): class Test1469(TestCase): def setUp(self): self.t = Task() - self.t('add foo') + self.t("add foo") self.t('add "neue Badmöbel kaufen"') def test_implicit_search_sensitive_regex(self): - """1469: Implicit search, case sensitive, regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=yes rc.regex=on') + """1469: Implicit search, case sensitive, regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=yes rc.regex=on") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) def test_implicit_search_sensitive_noregex(self): - """1469: Implicit search, case sensitive, no regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=yes rc.regex=off') + """1469: Implicit search, case sensitive, no regex""" + code, out, err = self.t( + "list /möbel/ rc.search.case.sensitive=yes rc.regex=off" + ) self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) - @unittest.skipIf('CYGWIN' in platform.system(), 'Skipping regex case-insensitive test for Cygwin') + @unittest.skipIf( + "CYGWIN" in platform.system(), "Skipping regex case-insensitive test for Cygwin" + ) def test_implicit_search_insensitive_regex(self): - """1469: Implicit search, case insensitive, regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=no rc.regex=on') - self.assertEqual(0, code, - "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + """1469: Implicit search, case insensitive, regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=no rc.regex=on") + self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) def test_implicit_search_insensitive_noregex(self): - """1469: Implicit search, case insensitive, no regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=no rc.regex=off') + """1469: Implicit search, case insensitive, no regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=no rc.regex=off") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) def test_explicit_search_sensitive_regex(self): - """1469: Explicit search, case sensitive, regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=yes rc.regex=on') + """1469: Explicit search, case sensitive, regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=yes rc.regex=on") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) def test_explicit_search_sensitive_noregex(self): - """1469: Explicit search, case sensitive, no regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=yes rc.regex=off') + """1469: Explicit search, case sensitive, no regex""" + code, out, err = self.t( + "list /möbel/ rc.search.case.sensitive=yes rc.regex=off" + ) self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) - @unittest.skipIf('CYGWIN' in platform.system(), 'Skipping regex case-insensitive test for Cygwin') + @unittest.skipIf( + "CYGWIN" in platform.system(), "Skipping regex case-insensitive test for Cygwin" + ) def test_explicit_search_insensitive_regex(self): - """1469: Explicit search, case insensitive, regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=no rc.regex=on') + """1469: Explicit search, case insensitive, regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=no rc.regex=on") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) def test_explicit_search_insensitive_noregex(self): - """1469: Explicit search, case insensitive, no regex """ - code, out, err = self.t('list /möbel/ rc.search.case.sensitive=no rc.regex=off') + """1469: Explicit search, case insensitive, no regex""" + code, out, err = self.t("list /möbel/ rc.search.case.sensitive=no rc.regex=off") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('möbel', out) - self.assertNotIn('foo', out) + self.assertIn("möbel", out) + self.assertNotIn("foo", out) class TestBug1479(TestCase): @@ -263,7 +272,7 @@ class TestBug1479(TestCase): self.t("add project:P1 one") self.t("add project:P2 one two") - code, out, err = self.t("description:one\ two list") + code, out, err = self.t(r"description:one\ two list") self.assertNotIn("P1", out) self.assertIn("P2", out) @@ -273,6 +282,7 @@ class TestBug1479(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/sequence.t b/test/sequence.test.py similarity index 92% rename from test/sequence.t rename to test/sequence.test.py index 33a6e826c..2943fdac2 100755 --- a/test/sequence.t +++ b/test/sequence.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -58,7 +58,10 @@ class TestSequences(TestCase): """Test sequences in start/stop""" self.t("1,2 start") code, out, err = self.t("_get 1.start 2.start") - self.assertRegex(out, "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2} \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\n") + self.assertRegex( + out, + "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2} \\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\n", + ) self.t("1,2 stop") code, out, err = self.t("_get 1.start 2.start") @@ -85,12 +88,15 @@ class TestSequences(TestCase): def test_sequence_annotate(self): """Test sequences in annotate""" self.t("1,2 annotate note") - code, out, err = self.t("_get 1.annotations.1.description 2.annotations.1.description") + code, out, err = self.t( + "_get 1.annotations.1.description 2.annotations.1.description" + ) self.assertEqual("note note\n", out) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/shell.t b/test/shell.test.py similarity index 93% rename from test/shell.t rename to test/shell.test.py index f219be770..1e4d212a5 100755 --- a/test/shell.t +++ b/test/shell.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,19 +41,20 @@ class TestFilterPrefix(TestCase): """Executed once before any test in the class""" cls.t = Task() cls.t.config("verbose", "nothing") - cls.t('add foo') + cls.t("add foo") def test_success(self): """Test successful search returns zero.""" - code, out, err = self.t('list /foo/') + code, out, err = self.t("list /foo/") def test_failure(self): """Test failed search returns non-zero.""" - code, out, err = self.t.runError('list /bar/') + code, out, err = self.t.runError("list /bar/") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/show.t b/test/show.test.py similarity index 96% rename from test/show.t rename to test/show.test.py index 12d03a13c..f3fdceb79 100755 --- a/test/show.t +++ b/test/show.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -69,7 +69,9 @@ class TestShowCommand(TestCase): """Verify show command lists all with no arg provided""" self.t.config("foo", "bar") code, out, err = self.t("show") - self.assertIn("Your .taskrc file contains these unrecognized variables:\n foo", out) + self.assertIn( + "Your .taskrc file contains these unrecognized variables:\n foo", out + ) class TestShowHelperCommand(TestCase): @@ -86,6 +88,7 @@ class TestShowHelperCommand(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/simpletap/CMakeLists.txt b/test/simpletap/CMakeLists.txt new file mode 100644 index 000000000..53303a1f5 --- /dev/null +++ b/test/simpletap/CMakeLists.txt @@ -0,0 +1 @@ +configure_file(__init__.py __init__.py COPYONLY) diff --git a/test/simpletap/__init__.py b/test/simpletap/__init__.py index 72b1fe690..4134f8f21 100644 --- a/test/simpletap/__init__.py +++ b/test/simpletap/__init__.py @@ -80,8 +80,7 @@ class TAPTestResult(unittest.result.TestResult): pass def _restoreStdout(self): - """Restore sys.stdout and sys.stderr, don't merge buffered output yet - """ + """Restore sys.stdout and sys.stderr, don't merge buffered output yet""" if self.buffer: sys.stdout = self._original_stdout sys.stderr = self._original_stderr @@ -99,12 +98,11 @@ class TAPTestResult(unittest.result.TestResult): else: stream.write("# " + line) - if not line.endswith('\n'): - stream.write('\n') + if not line.endswith("\n"): + stream.write("\n") def _mergeStdout(self): - """Merge buffered output with main streams - """ + """Merge buffered output with main streams""" if self.buffer: output = self._stdout_buffer.getvalue() @@ -154,25 +152,33 @@ class TAPTestResult(unittest.result.TestResult): if status: if status == "SKIP": - self.stream.writeln("{0} {1} - {2}: {3} # skip".format( - color("ok", "yellow"), self.testsRun, filename, desc) + self.stream.writeln( + "{0} {1} - {2}: {3} # skip".format( + color("ok", "yellow"), self.testsRun, filename, desc + ) ) elif status == "EXPECTED_FAILURE": - self.stream.writeln("{0} {1} - {2}: {3} # TODO".format( - color("not ok", "yellow"), self.testsRun, filename, desc) + self.stream.writeln( + "{0} {1} - {2}: {3} # TODO".format( + color("not ok", "yellow"), self.testsRun, filename, desc + ) ) elif status == "UNEXPECTED_SUCCESS": - self.stream.writeln("{0} {1} - {2}: {3} # FIXED".format( - color("not ok", "yellow"), self.testsRun, filename, desc) + self.stream.writeln( + "{0} {1} - {2}: {3} # FIXED".format( + color("not ok", "yellow"), self.testsRun, filename, desc + ) ) else: - self.stream.writeln("{0} {1} - {2}: {3}".format( - color("not ok", "red"), self.testsRun, filename, desc) + self.stream.writeln( + "{0} {1} - {2}: {3}".format( + color("not ok", "red"), self.testsRun, filename, desc + ) ) if exception_name: - self.stream.writeln("# {0}: {1}{2}:".format( - status, exception_name, trace_msg) + self.stream.writeln( + "# {0}: {1}{2}:".format(status, exception_name, trace_msg) ) else: self.stream.writeln("# {0}:".format(status)) @@ -185,8 +191,10 @@ class TAPTestResult(unittest.result.TestResult): line = line.replace("\\n", "\n# ") self.stream.writeln("#{0}{1}".format(padding, line)) else: - self.stream.writeln("{0} {1} - {2}: {3}".format( - color("ok", "green"), self.testsRun, filename, desc) + self.stream.writeln( + "{0} {1} - {2}: {3}".format( + color("ok", "green"), self.testsRun, filename, desc + ) ) # Flush all buffers to stdout @@ -223,6 +231,7 @@ class TAPTestRunner(unittest.runner.TextTestRunner): Inherits from TextTestRunner the default runner. """ + resultclass = TAPTestResult def __init__(self, stream=sys.stdout, *args, **kwargs): diff --git a/test/sorting.t b/test/sorting.t deleted file mode 100755 index 144369756..000000000 --- a/test/sorting.t +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -############################################################################### -# -# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -# -# 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. -# -# https://www.opensource.org/licenses/mit-license.php -# -############################################################################### - -import sys -import os -import re -import unittest -import time -# Ensure python finds the local simpletap module -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from basetest import Task, TestCase -from basetest.meta import MetaTest - - -class MetaTestSorting(MetaTest): - """Helper metaclass to simplify test logic below (TestSorting) - - Creates test_methods in the TestCase class dynamically named after the - filter used. - """ - @staticmethod - def make_function(classname, *args, **kwargs): - _filter, expectations = args - - def test(self): - # ### Body of the usual test_testcase ### # - code, out, err = self.t( - "rc.report.{0}.sort:{1} {0}".format(self._report, _filter) - ) - - for expected in expectations: - regex = re.compile(expected, re.DOTALL) - self.assertRegex(out, regex) - - # Title of test in report - test.__doc__ = "{0} sort:{1}".format(classname, _filter) - - return test - - -class TestSorting(TestCase): - __metaclass__ = MetaTestSorting - - @classmethod - def setUpClass(cls): - cls.t = Task() - - # Report to use when running this class's tests - cls._report = "list" - - cls.t("add zero") - cls.t("add priority:H project:A due:yesterday one") - cls.t("add priority:M project:B due:today two") - cls.t("add priority:L project:C due:tomorrow three") - cls.t("add priority:H project:C due:today four") - cls.t("2 start") - - TESTS = ( - # Filter # Expected matches/outputs - - # Single sort column. - ('priority-', ('(?:one.+four|four.+one).+two.+three.+zero',)), - ('priority+', ('zero.+three.+two.+(?:one.+four|four.+one)',)), - ('project-', ('(?:three.+four|four.+three).+two.+one.+zero',)), - ('project+', ('zero.+one.+two.+(?:three.+four|four.+three)',)), - ('start-', ('one.+zero', 'one.+two', 'one.+three', 'one.+four',)), - ('start+', ('one.+zero', 'one.+two', 'one.+three', 'one.+four',)), - ('due-', ('three.+(?:two.+four|four.+two).+one.+zero',)), - ('due+', ('one.+(?:two.+four|four.+two).+three.+zero',)), - ('description-', ('zero.+two.+three.+one.+four',)), - ('description+', ('four.+one.+three.+two.+zero',)), - - # Two sort columns. - ('priority-,project-', ('four.+one.+two.+three.+zero',)), - ('priority-,project+', ('one.+four.+two.+three.+zero',)), - ('priority+,project-', ('zero.+three.+two.+four.+one',)), - ('priority+,project+', ('zero.+three.+two.+one.+four',)), - - ('priority-,start-', ('one.+four.+two.+three.+zero',)), - ('priority-,start+', ('one.+four.+two.+three.+zero',)), - ('priority+,start-', ('zero.+three.+two.+one.+four',)), - ('priority+,start+', ('zero.+three.+two.+one.+four',)), - - ('priority-,due-', ('four.+one.+two.+three.+zero',)), - ('priority-,due+', ('one.+four.+two.+three.+zero',)), - ('priority+,due-', ('zero.+three.+two.+four.+one',)), - ('priority+,due+', ('zero.+three.+two.+one.+four',)), - - ('priority-,description-', ('one.+four.+two.+three.+zero',)), - ('priority-,description+', ('four.+one.+two.+three.+zero',)), - ('priority+,description-', ('zero.+three.+two.+one.+four',)), - ('priority+,description+', ('zero.+three.+two.+four.+one',)), - - ('project-,priority-', ('four.+three.+two.+one.+zero',)), - ('project-,priority+', ('three.+four.+two.+one.+zero',)), - ('project+,priority-', ('zero.+one.+two.+four.+three',)), - ('project+,priority+', ('zero.+one.+two.+three.+four',)), - - ('project-,start-', ('three.+four.+two.+one.+zero',)), - ('project-,start+', ('(?:four.+three|three.+four).+two.+one.+zero',)), - ('project+,start-', ('zero.+one.+two.+three.+four',)), - ('project+,start+', ('zero.+one.+two.+(?:four.+three|three.+four)',)), - - ('project-,due-', ('three.+four.+two.+one.+zero',)), - ('project-,due+', ('four.+three.+two.+one.+zero',)), - ('project+,due-', ('zero.+one.+two.+three.+four',)), - ('project+,due+', ('zero.+one.+two.+four.+three',)), - - ('project-,description-', ('three.+four.+two.+one.+zero',)), - ('project-,description+', ('four.+three.+two.+one.+zero',)), - ('project+,description-', ('zero.+one.+two.+three.+four',)), - ('project+,description+', ('zero.+one.+two.+four.+three',)), - - ('start-,priority-', ('one.+four.+two.+three.+zero',)), - ('start-,priority+', ('one.+zero.+three.+two.+four',)), - ('start+,priority-', ('one.+four.+two.+three.+zero',)), - ('start+,priority+', ('one.+zero.+three.+two.+four',)), - - ('start-,project-', ('one.+(?:three.+four|four.+three).+two.+zero',)), - ('start-,project+', ('one.+zero.+two.+(?:three.+four|four.+three)',)), - ('start+,project-', ('one.+(?:three.+four|four.+three).+two.+zero',)), - ('start+,project+', ('one.+zero.+two.+(?:three.+four|four.+three)',)), - - ('start-,due-', ('one.+three.+(?:four.+two|two.+four).+zero',)), - ('start-,due+', ('one.+(?:four.+two|two.+four).+three.+zero',)), - ('start+,due-', ('one.+three.+(?:four.+two|two.+four).+zero',)), - ('start+,due+', ('one.+(?:four.+two|two.+four).+three.+zero',)), - - ('start-,description-', ('one.+zero.+two.+three.+four',)), - ('start-,description+', ('one.+four.+three.+two.+zero',)), - ('start+,description-', ('one.+zero.+two.+three.+four',)), - ('start+,description+', ('one.+four.+three.+two.+zero',)), - - ('due-,priority-', ('three.+four.+two.+one.+zero',)), - ('due-,priority+', ('three.+two.+four.+one.+zero',)), - ('due+,priority-', ('one.+four.+two.+three.+zero',)), - ('due+,priority+', ('one.+two.+four.+three.+zero',)), - - ('due-,project-', ('three.+four.+two.+one.+zero',)), - ('due-,project+', ('three.+two.+four.+one.+zero',)), - ('due+,project-', ('one.+four.+two.+three.+zero',)), - ('due+,project+', ('one.+two.+four.+three.+zero',)), - - ('due-,start-', ('three.+(?:four.+two|two.+four).+one.+zero',)), - ('due-,start+', ('three.+(?:four.+two|two.+four).+one.+zero',)), - ('due+,start-', ('one.+(?:four.+two|two.+four).+three.+zero',)), - ('due+,start+', ('one.+(?:four.+two|two.+four).+three.+zero',)), - - ('due-,description-', ('three.+two.+four.+one.+zero',)), - ('due-,description+', ('three.+four.+two.+one.+zero',)), - ('due+,description-', ('one.+two.+four.+three.+zero',)), - ('due+,description+', ('one.+four.+two.+three.+zero',)), - - ('description-,priority-', ('zero.+two.+three.+one.+four',)), - ('description-,priority+', ('zero.+two.+three.+one.+four',)), - ('description+,priority-', ('four.+one.+three.+two.+zero',)), - ('description+,priority+', ('four.+one.+three.+two.+zero',)), - - ('description-,project-', ('zero.+two.+three.+one.+four',)), - ('description-,project+', ('zero.+two.+three.+one.+four',)), - ('description+,project-', ('four.+one.+three.+two.+zero',)), - ('description+,project+', ('four.+one.+three.+two.+zero',)), - - ('description-,start-', ('zero.+two.+three.+one.+four',)), - ('description-,start+', ('zero.+two.+three.+one.+four',)), - ('description+,start-', ('four.+one.+three.+two.+zero',)), - ('description+,start+', ('four.+one.+three.+two.+zero',)), - - ('description-,due-', ('zero.+two.+three.+one.+four',)), - ('description-,due+', ('zero.+two.+three.+one.+four',)), - ('description+,due-', ('four.+one.+three.+two.+zero',)), - ('description+,due+', ('four.+one.+three.+two.+zero',)), - - # Four sort columns. - ('start+,project+,due+,priority+', ('one.+zero.+two.+four.+three',)), - ('project+,due+,priority+,start+', ('zero.+one.+two.+four.+three',)), - ) - - -class TestBug438(TestCase): - __metaclass__ = MetaTestSorting - - # Bug #438: Reports sorting by end, start, and entry are ordered - # incorrectly, if time is included. - @classmethod - def setUpClass(cls): - cls.t = Task() - - # Report to use when running this class's tests - cls._report = "foo" - - cls.t.config("dateformat", "SNHDMY") - cls.t.config("report.foo.columns", "entry,start,end,description") - cls.t.config("report.foo.dateformat", "SNHDMY") - - # Preparing data: - # 2 tasks created in the past, 1 second apart - # 2 tasks created in the past, and started 10 seconds later - # 2 tasks created in the past, and finished 20 seconds later - - stamp = int(time.time()) - cls.t("add one older entry:{0}".format(stamp)) - stamp += 1 - cls.t("add one newer entry:{0}".format(stamp)) - - start = stamp + 10 - cls.t("add two older entry:{0} start:{1}".format(stamp, start)) - start += 1 - cls.t("add two newer entry:{0} start:{1}".format(stamp, start)) - - end = start + 10 - cls.t("log three older entry:{0} end:{1}".format(stamp, end)) - end += 1 - cls.t("log three newer entry:{0} end:{1}".format(stamp, end)) - - TESTS = { - ("entry+", ("one older.+one newer",)), - ("entry-", ("one newer.+one older",)), - ("start+", ("two older.+two newer",)), - ("start-", ("two newer.+two older",)), - ("end+", ("three older.+three newer",)), - ("end-", ("three newer.+three older",)), - } - - -class TestSortNone(TestCase): - def setUp(self): - self.t = Task() - - def test_sort_none(self): - """Verify that 'sort:none' removes all sorting""" - self.t("add one") - self.t("add two") - self.t("add three") - code, out, err = self.t("_get 1.uuid 2.uuid 3.uuid") - uuid1, uuid2, uuid3 = out.strip().split(' ') - code, out, err = self.t("%s %s %s list rc.report.list.sort:none rc.report.list.columns:id,description rc.report.list.labels:id,desc" % (uuid2, uuid3, uuid1)) - self.assertRegex(out, ' 2 two\n 3 three\n 1 one') - - -if __name__ == "__main__": - from simpletap import TAPTestRunner - unittest.main(testRunner=TAPTestRunner()) - -# vim: ai sts=4 et sw=4 ft=python diff --git a/test/sorting.test.py b/test/sorting.test.py new file mode 100755 index 000000000..f8927d04f --- /dev/null +++ b/test/sorting.test.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import re +import unittest +import time + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase +from basetest.meta import MetaTest + + +class MetaTestSorting(MetaTest): + """Helper metaclass to simplify test logic below (TestSorting) + + Creates test_methods in the TestCase class dynamically named after the + filter used. + """ + + @staticmethod + def make_function(classname, *args, **kwargs): + _filter, expectations = args + + def test(self): + # ### Body of the usual test_testcase ### # + code, out, err = self.t( + "rc.report.{0}.sort:{1} {0}".format(self._report, _filter) + ) + + for expected in expectations: + regex = re.compile(expected, re.DOTALL) + self.assertRegex(out, regex) + + # Title of test in report + test.__doc__ = "{0} sort:{1}".format(classname, _filter) + + return test + + +class TestSorting(TestCase): + __metaclass__ = MetaTestSorting + + @classmethod + def setUpClass(cls): + cls.t = Task() + + # Report to use when running this class's tests + cls._report = "list" + + cls.t("add zero") + cls.t("add priority:H project:A due:yesterday one") + cls.t("add priority:M project:B due:today two") + cls.t("add priority:L project:C due:tomorrow three") + cls.t("add priority:H project:C due:today four") + cls.t("2 start") + + TESTS = ( + # Filter # Expected matches/outputs + # Single sort column. + ("priority-", ("(?:one.+four|four.+one).+two.+three.+zero",)), + ("priority+", ("zero.+three.+two.+(?:one.+four|four.+one)",)), + ("project-", ("(?:three.+four|four.+three).+two.+one.+zero",)), + ("project+", ("zero.+one.+two.+(?:three.+four|four.+three)",)), + ( + "start-", + ( + "one.+zero", + "one.+two", + "one.+three", + "one.+four", + ), + ), + ( + "start+", + ( + "one.+zero", + "one.+two", + "one.+three", + "one.+four", + ), + ), + ("due-", ("three.+(?:two.+four|four.+two).+one.+zero",)), + ("due+", ("one.+(?:two.+four|four.+two).+three.+zero",)), + ("description-", ("zero.+two.+three.+one.+four",)), + ("description+", ("four.+one.+three.+two.+zero",)), + # Two sort columns. + ("priority-,project-", ("four.+one.+two.+three.+zero",)), + ("priority-,project+", ("one.+four.+two.+three.+zero",)), + ("priority+,project-", ("zero.+three.+two.+four.+one",)), + ("priority+,project+", ("zero.+three.+two.+one.+four",)), + ("priority-,start-", ("one.+four.+two.+three.+zero",)), + ("priority-,start+", ("one.+four.+two.+three.+zero",)), + ("priority+,start-", ("zero.+three.+two.+one.+four",)), + ("priority+,start+", ("zero.+three.+two.+one.+four",)), + ("priority-,due-", ("four.+one.+two.+three.+zero",)), + ("priority-,due+", ("one.+four.+two.+three.+zero",)), + ("priority+,due-", ("zero.+three.+two.+four.+one",)), + ("priority+,due+", ("zero.+three.+two.+one.+four",)), + ("priority-,description-", ("one.+four.+two.+three.+zero",)), + ("priority-,description+", ("four.+one.+two.+three.+zero",)), + ("priority+,description-", ("zero.+three.+two.+one.+four",)), + ("priority+,description+", ("zero.+three.+two.+four.+one",)), + ("project-,priority-", ("four.+three.+two.+one.+zero",)), + ("project-,priority+", ("three.+four.+two.+one.+zero",)), + ("project+,priority-", ("zero.+one.+two.+four.+three",)), + ("project+,priority+", ("zero.+one.+two.+three.+four",)), + ("project-,start-", ("three.+four.+two.+one.+zero",)), + ("project-,start+", ("(?:four.+three|three.+four).+two.+one.+zero",)), + ("project+,start-", ("zero.+one.+two.+three.+four",)), + ("project+,start+", ("zero.+one.+two.+(?:four.+three|three.+four)",)), + ("project-,due-", ("three.+four.+two.+one.+zero",)), + ("project-,due+", ("four.+three.+two.+one.+zero",)), + ("project+,due-", ("zero.+one.+two.+three.+four",)), + ("project+,due+", ("zero.+one.+two.+four.+three",)), + ("project-,description-", ("three.+four.+two.+one.+zero",)), + ("project-,description+", ("four.+three.+two.+one.+zero",)), + ("project+,description-", ("zero.+one.+two.+three.+four",)), + ("project+,description+", ("zero.+one.+two.+four.+three",)), + ("start-,priority-", ("one.+four.+two.+three.+zero",)), + ("start-,priority+", ("one.+zero.+three.+two.+four",)), + ("start+,priority-", ("one.+four.+two.+three.+zero",)), + ("start+,priority+", ("one.+zero.+three.+two.+four",)), + ("start-,project-", ("one.+(?:three.+four|four.+three).+two.+zero",)), + ("start-,project+", ("one.+zero.+two.+(?:three.+four|four.+three)",)), + ("start+,project-", ("one.+(?:three.+four|four.+three).+two.+zero",)), + ("start+,project+", ("one.+zero.+two.+(?:three.+four|four.+three)",)), + ("start-,due-", ("one.+three.+(?:four.+two|two.+four).+zero",)), + ("start-,due+", ("one.+(?:four.+two|two.+four).+three.+zero",)), + ("start+,due-", ("one.+three.+(?:four.+two|two.+four).+zero",)), + ("start+,due+", ("one.+(?:four.+two|two.+four).+three.+zero",)), + ("start-,description-", ("one.+zero.+two.+three.+four",)), + ("start-,description+", ("one.+four.+three.+two.+zero",)), + ("start+,description-", ("one.+zero.+two.+three.+four",)), + ("start+,description+", ("one.+four.+three.+two.+zero",)), + ("due-,priority-", ("three.+four.+two.+one.+zero",)), + ("due-,priority+", ("three.+two.+four.+one.+zero",)), + ("due+,priority-", ("one.+four.+two.+three.+zero",)), + ("due+,priority+", ("one.+two.+four.+three.+zero",)), + ("due-,project-", ("three.+four.+two.+one.+zero",)), + ("due-,project+", ("three.+two.+four.+one.+zero",)), + ("due+,project-", ("one.+four.+two.+three.+zero",)), + ("due+,project+", ("one.+two.+four.+three.+zero",)), + ("due-,start-", ("three.+(?:four.+two|two.+four).+one.+zero",)), + ("due-,start+", ("three.+(?:four.+two|two.+four).+one.+zero",)), + ("due+,start-", ("one.+(?:four.+two|two.+four).+three.+zero",)), + ("due+,start+", ("one.+(?:four.+two|two.+four).+three.+zero",)), + ("due-,description-", ("three.+two.+four.+one.+zero",)), + ("due-,description+", ("three.+four.+two.+one.+zero",)), + ("due+,description-", ("one.+two.+four.+three.+zero",)), + ("due+,description+", ("one.+four.+two.+three.+zero",)), + ("description-,priority-", ("zero.+two.+three.+one.+four",)), + ("description-,priority+", ("zero.+two.+three.+one.+four",)), + ("description+,priority-", ("four.+one.+three.+two.+zero",)), + ("description+,priority+", ("four.+one.+three.+two.+zero",)), + ("description-,project-", ("zero.+two.+three.+one.+four",)), + ("description-,project+", ("zero.+two.+three.+one.+four",)), + ("description+,project-", ("four.+one.+three.+two.+zero",)), + ("description+,project+", ("four.+one.+three.+two.+zero",)), + ("description-,start-", ("zero.+two.+three.+one.+four",)), + ("description-,start+", ("zero.+two.+three.+one.+four",)), + ("description+,start-", ("four.+one.+three.+two.+zero",)), + ("description+,start+", ("four.+one.+three.+two.+zero",)), + ("description-,due-", ("zero.+two.+three.+one.+four",)), + ("description-,due+", ("zero.+two.+three.+one.+four",)), + ("description+,due-", ("four.+one.+three.+two.+zero",)), + ("description+,due+", ("four.+one.+three.+two.+zero",)), + # Four sort columns. + ("start+,project+,due+,priority+", ("one.+zero.+two.+four.+three",)), + ("project+,due+,priority+,start+", ("zero.+one.+two.+four.+three",)), + ) + + +class TestBug438(TestCase): + __metaclass__ = MetaTestSorting + + # Bug #438: Reports sorting by end, start, and entry are ordered + # incorrectly, if time is included. + @classmethod + def setUpClass(cls): + cls.t = Task() + + # Report to use when running this class's tests + cls._report = "foo" + + cls.t.config("dateformat", "SNHDMY") + cls.t.config("report.foo.columns", "entry,start,end,description") + cls.t.config("report.foo.dateformat", "SNHDMY") + + # Preparing data: + # 2 tasks created in the past, 1 second apart + # 2 tasks created in the past, and started 10 seconds later + # 2 tasks created in the past, and finished 20 seconds later + + stamp = int(time.time()) + cls.t("add one older entry:{0}".format(stamp)) + stamp += 1 + cls.t("add one newer entry:{0}".format(stamp)) + + start = stamp + 10 + cls.t("add two older entry:{0} start:{1}".format(stamp, start)) + start += 1 + cls.t("add two newer entry:{0} start:{1}".format(stamp, start)) + + end = start + 10 + cls.t("log three older entry:{0} end:{1}".format(stamp, end)) + end += 1 + cls.t("log three newer entry:{0} end:{1}".format(stamp, end)) + + TESTS = { + ("entry+", ("one older.+one newer",)), + ("entry-", ("one newer.+one older",)), + ("start+", ("two older.+two newer",)), + ("start-", ("two newer.+two older",)), + ("end+", ("three older.+three newer",)), + ("end-", ("three newer.+three older",)), + } + + +class TestSortNone(TestCase): + def setUp(self): + self.t = Task() + + def test_sort_none(self): + """Verify that 'sort:none' removes all sorting""" + self.t("add one") + self.t("add two") + self.t("add three") + code, out, err = self.t("_get 1.uuid 2.uuid 3.uuid") + uuid1, uuid2, uuid3 = out.strip().split(" ") + code, out, err = self.t( + "%s %s %s list rc.report.list.sort:none rc.report.list.columns:id,description rc.report.list.labels:id,desc" + % (uuid2, uuid3, uuid1) + ) + self.assertRegex(out, " 2 two\n 3 three\n 1 one") + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/special.t b/test/special.test.py similarity index 86% rename from test/special.t rename to test/special.test.py index 59575a043..a1ed28130 100755 --- a/test/special.t +++ b/test/special.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,24 +28,26 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase + class TestSpecialTags(TestCase): @classmethod def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() cls.t.config("color.keyword.red", "red") - cls.t.config("color.alternate", "") - cls.t.config("color.tagged", "") - cls.t.config("color.pri.H", "") - cls.t.config("color.completed", "") - cls.t.config("nag", "NAG") - cls.t.config("color", "1") - cls.t.config("_forcecolor", "1") + cls.t.config("color.alternate", "") + cls.t.config("color.tagged", "") + cls.t.config("color.pri.H", "") + cls.t.config("color.completed", "") + cls.t.config("nag", "NAG") + cls.t.config("color", "1") + cls.t.config("_forcecolor", "1") def test_nocolor(self): self.t("add should have no red +nocolor priority:H") @@ -64,6 +65,7 @@ class TestSpecialTags(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/start.t b/test/start.test.py similarity index 95% rename from test/start.t rename to test/start.test.py index e8ea3d469..c87b58f36 100755 --- a/test/start.t +++ b/test/start.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -82,9 +82,9 @@ class TestStart(TestCase): def test_journal_annotations(self): """Verify journal start/stop annotations are used""" - self.t.config("journal.time", "1") + self.t.config("journal.time", "1") self.t.config("journal.time.start.annotation", "Nu kör vi") - self.t.config("journal.time.stop.annotation", "Nu stannar vi") + self.t.config("journal.time.stop.annotation", "Nu stannar vi") self.t("add one") self.t("1 start") @@ -98,7 +98,7 @@ class TestStart(TestCase): def test_start_remove_end(self): """Verify that starting a task removes end timestamp""" self.t("add one") - uuid = self.t('_get 1.uuid')[1].strip() + uuid = self.t("_get 1.uuid")[1].strip() self.t("1 done") task = self.t.export()[0] @@ -130,7 +130,7 @@ class TestActiveTaskHandling(TestCase): def test_start_nothing(self): """Verify error message when no tasks are specified""" - code, out, err = self.t.runError ("999 start") + code, out, err = self.t.runError("999 start") self.assertIn("No tasks specified.", err) def test_start_started(self): @@ -161,6 +161,7 @@ class TestFeature608(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/stats.t b/test/stats.test.py similarity index 89% rename from test/stats.t rename to test/stats.test.py index cc1257964..1d2f32e9c 100755 --- a/test/stats.t +++ b/test/stats.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -48,17 +48,18 @@ class TestStatisticsCommand(TestCase): self.t("log three") code, out, err = self.t("stats") - self.assertRegex(out, "Pending\s+1\n") - self.assertRegex(out, "Completed\s+1\n") - self.assertRegex(out, "Deleted\s+1\n") - self.assertRegex(out, "Total\s+3\n") + self.assertRegex(out, "Pending\\s+1\n") + self.assertRegex(out, "Completed\\s+1\n") + self.assertRegex(out, "Deleted\\s+1\n") + self.assertRegex(out, "Total\\s+3\n") code, out, err = self.t("stats rc._forcecolor:on") - self.assertRegex(out, "Pending\s+1\n") + self.assertRegex(out, "Pending\\s+1\n") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/stress_test b/test/stress_test deleted file mode 100755 index 73975127f..000000000 --- a/test/stress_test +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -from __future__ import print_function -from subprocess import call -import argparse -import glob -import logging -import os -import sys - - -def find_tests(): - tests = [] - - for test in glob.glob("*.t") + glob.glob("*.t.exe"): - if os.access(test, os.X_OK): - # Executables only - tests.append(test) - else: - log.debug("Ignored test %s as it is not executable", test) - - log.info("Tests found: %s", len(tests)) - - return tests - - -def run_test(test): - log.warn("Running test %s %s times", test, cmd_args.repeat) - - if not test.startswith(".{0}".format(os.path.sep)): - test = ".{0}{1}".format(os.path.sep, test) - - for i in range(cmd_args.repeat): - exit = call([test], stdout=sys.stdout, stderr=sys.stderr) - - if exit != 0: - log.error("Failed to run test %s on repetition %s", test, i) - break - - -def parse_args(): - parser = argparse.ArgumentParser(description="Run Taskwarrior tests repeatedly") - parser.add_argument('--logging', '-l', action="count", default=0, - help="Logging level. -lll is the highest level") - parser.add_argument('--repeat', metavar="N", type=int, default=100, - help="How many times to run each test (default: 100)") - parser.add_argument('--noprompt', action="store_true", - help="Do not prompt when running all tests") - parser.add_argument('test', nargs="*", - help="Test files to run repeatedly. Unspecified = all tests") - return parser.parse_args() - - -def main(): - if cmd_args.test: - for test in cmd_args.test: - run_test(test) - else: - if not cmd_args.noprompt: - r = raw_input("No test was specified, are you sure you want to run all tests? (y/N)\n") - if not (r and r[0] in ["y", "Y"]): - return - - log.info("Stress testing all tests") - for test in find_tests(): - run_test(test) - - -if __name__ == "__main__": - cmd_args = parse_args() - - if cmd_args.logging == 1: - level = logging.WARN - elif cmd_args.logging == 2: - level = logging.INFO - elif cmd_args.logging >= 3: - level = logging.DEBUG - else: - level = logging.ERROR - - logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(message)s", - level=level, - ) - log = logging.getLogger(__name__) - - log.debug("Parsed commandline arguments: %s", cmd_args) - - try: - main() - except Exception as e: - log.exception(e) - sys.exit(1) diff --git a/test/substitute.t b/test/substitute.test.py similarity index 99% rename from test/substitute.t rename to test/substitute.test.py index 04860e759..845076b25 100755 --- a/test/substitute.t +++ b/test/substitute.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -108,6 +108,7 @@ class TestBug441(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/sugar.t b/test/sugar.test.py similarity index 96% rename from test/sugar.t rename to test/sugar.test.py index 3c4227077..742138593 100755 --- a/test/sugar.t +++ b/test/sugar.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,9 +39,9 @@ class TestSugar(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() - self.t('add one') - self.t('add two') - self.t('add three') + self.t("add one") + self.t("add two") + self.t("add three") def test_empty_conjunction(self): """Test syntax that mathematicians find sane and expected""" @@ -71,6 +71,7 @@ class TestSugar(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/summary.t b/test/summary.test.py similarity index 93% rename from test/summary.t rename to test/summary.test.py index 5f6445f39..8464aa51a 100755 --- a/test/summary.t +++ b/test/summary.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -73,9 +73,7 @@ class TestBug1904(TestCase): self.t("add pro:a.b test2") def validate_order(self, out): - order = ("a-b", - "a", - " b") + order = ("a-b", "a", " b") lines = out.splitlines(True) # position where project names start on the lines list @@ -86,8 +84,10 @@ class TestBug1904(TestCase): self.assertTrue( lines[pos].startswith(proj), - msg=("Project '{0}' is not in line #{1} or has an unexpected " - "indentation.{2}".format(proj, pos, out)) + msg=( + "Project '{0}' is not in line #{1} or has an unexpected " + "indentation.{2}".format(proj, pos, out) + ), ) def test_project_eval(self): @@ -99,6 +99,7 @@ class TestBug1904(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/t.t.cpp b/test/t.t.cpp deleted file mode 100644 index 69ac90a11..000000000 --- a/test/t.t.cpp +++ /dev/null @@ -1,257 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest test (49); - - // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); - - test.is ((int)Task::textToStatus ("pending"), (int)Task::pending, "textToStatus pending"); - test.is ((int)Task::textToStatus ("completed"), (int)Task::completed, "textToStatus completed"); - test.is ((int)Task::textToStatus ("deleted"), (int)Task::deleted, "textToStatus deleted"); - test.is ((int)Task::textToStatus ("recurring"), (int)Task::recurring, "textToStatus recurring"); - - test.is (Task::statusToText (Task::pending), "pending", "statusToText pending"); - test.is (Task::statusToText (Task::completed), "completed", "statusToText completed"); - test.is (Task::statusToText (Task::deleted), "deleted", "statusToText deleted"); - test.is (Task::statusToText (Task::recurring), "recurring", "statusToText recurring"); - - // Round-trip testing. - Task t3; - t3.set ("name", "value"); - std::string before = t3.composeF4 (); - t3.parse (before); - std::string after = t3.composeF4 (); - t3.parse (after); - after = t3.composeF4 (); - t3.parse (after); - after = t3.composeF4 (); - test.is (before, after, "Task::composeF4 -> parse round trip 4 iterations"); - - // Legacy Format 1 (no longer supported) - // [tags] [attributes] description\n - // X [tags] [attributes] description\n - std::string sample = "[tag1 tag2] [att1:value1 att2:value2] Description"; - sample = "X " - "[one two] " - "[att1:value1 att2:value2] " - "Description"; - bool good = true; - try { Task ff1 (sample); } catch (...) { good = false; } - test.notok (good, "Support for ff1 removed"); - - // Legacy Format 2 (no longer supported) - // uuid status [tags] [attributes] description\n - sample = "00000000-0000-0000-0000-000000000000 " - "- " - "[tag1 tag2] " - "[att1:value1 att2:value2] " - "Description"; - good = true; - try { Task ff2 (sample); } catch (...) { good = false; } - test.notok (good, "Support for ff2 removed"); - - // Legacy Format 3 - // uuid status [tags] [attributes] [annotations] description\n - sample = "00000000-0000-0000-0000-000000000000 " - "- " - "[tag1 tag2] " - "[att1:value1 att2:value2] " - "[123:ann1 456:ann2] Description"; - good = true; - try { Task ff3 (sample); } catch (...) { good = false; } - test.notok (good, "Support for ff3 removed"); - - // Current Format 4 - // [name:"value" ...]\n - sample = "[" - "uuid:\"00000000-0000-0000-0000-000000000000\" " - "status:\"pending\" " - "tags:\"tag1,tag2\" " - "att1:\"value1\" " - "att2:\"value2\" " - "description:\"Description\"" - "]"; - Task ff4 (sample); - std::string value = ff4.get ("uuid"); - test.is (value, "00000000-0000-0000-0000-000000000000", "ff4 uuid"); - value = ff4.get ("status"); - test.is (value, "pending", "ff4 status"); - test.ok (ff4.hasTag ("tag1"), "ff4 tag1"); - test.ok (ff4.hasTag ("tag2"), "ff4 tag2"); - test.is (ff4.getTagCount (), 2, "ff4 # tags"); - value = ff4.get ("att1"); - test.is (value, "value1", "ff4 att1"); - value = ff4.get ("att2"); - test.is (value, "value2", "ff4 att2"); - value = ff4.get ("description"); - test.is (value, "Description", "ff4 description"); - -/* - -TODO Task::composeCSV -TODO Task::composeYAML -TODO Task::id -TODO Task::*Status -TODO Task::*Tag* -TODO Task::*Annotation* - -TODO Task::addDependency -TODO Task::addDependency -TODO Task::removeDependency -TODO Task::removeDependency -TODO Task::getDependencies -TODO Task::getDependencies - -TODO Task::urgency - -TODO Task::encode -TODO Task::decode - -*/ - - // Task::operator== - Task left ("[one:1 two:2 three:3]"); - Task right (left); - test.ok (left == right, "left == right -> true"); - left.set ("one", "1.0"); - test.notok (left == right, "left == right -> false"); - - //////////////////////////////////////////////////////////////////////////////// - Task task; - - // (blank) - good = true; - try {task = Task ("");} - catch (const std::string& e){test.diag (e); good = false;} - test.notok (good, "Task::Task ('')"); - - // [] - good = true; - try {task = Task ("[]");} - catch (const std::string& e){test.diag (e); good = false;} - test.notok (good, "Task::Task ('[]')"); - - // [name:"value"] - good = true; - try {task = Task ("[name:\"value\"]");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, "Task::Task ('[name:\"value\"]')"); - test.is (task.get ("name"), "value", "name=value"); - - // [name:"one two"] - good = true; - try {task = Task ("[name:\"one two\"]");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, "Task::Task ('[name:\"one two\"]')"); - test.is (task.get ("name"), "one two", "name=one two"); - - // [one:two three:four] - good = true; - try {task = Task (R"([one:"two" three:"four"])");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, R"(Task::Task ('[one:"two" three:"four"]'))"); - test.is (task.get ("one"), "two", "one=two"); - test.is (task.get ("three"), "four", "three=four"); - - // Task::set - task = Task(); - task.set ("name", "value"); - test.is (task.composeF4 (), "[name:\"value\"]", "Task::set"); - - // Task::has - test.ok (task.has ("name"), "Task::has"); - test.notok (task.has ("woof"), "Task::has not"); - - // Task::get_int - task.set ("one", 1); - test.is (task.composeF4 (), R"([name:"value" one:"1"])", "Task::set"); - test.is (task.get_int ("one"), 1, "Task::get_int"); - - // Task::get_ulong - task.set ("two", "4294967295"); - test.is (task.composeF4 (), R"([name:"value" one:"1" two:"4294967295"])", "Task::set"); - test.is ((size_t)task.get_ulong ("two"), (size_t)4294967295UL, "Task::get_ulong"); - - // Task::remove - task.remove ("one"); - task.remove ("two"); - test.is (task.composeF4 (), "[name:\"value\"]", "Task::remove"); - - // Task::all - test.is (task.all ().size (), (size_t)1, "Task::all size"); - - //////////////////////////////////////////////////////////////////////////////// - - Task::attributes["description"] = "string"; - Task::attributes["entry"] = "date"; - Task::attributes["tags"] = "string"; - Task::attributes["uuid"] = "string"; - - good = true; - try {Task t4 ("{}");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, "Task::Task ('{}')"); - - good = true; - try {Task t5 (R"({"uuid":"00000000-0000-0000-000000000001","description":"foo","entry":"1234567890"})");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, "Task::Task ('{}')"); - - // Verify tag handling is correct between F4 and JSON. - Task t6; - t6.set ("entry", "20130602T224000Z"); - t6.set ("description", "DESC"); - t6.addTag ("tag1"); - test.is (t6.composeF4 (), R"([description:"DESC" entry:"20130602T224000Z" tags:"tag1"])", "F4 good"); - test.is (t6.composeJSON (), R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1"]})", "JSON good"); - - t6.addTag ("tag2"); - test.is (t6.composeF4 (), R"([description:"DESC" entry:"20130602T224000Z" tags:"tag1,tag2"])", "F4 good"); - test.is (t6.composeJSON (), R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})", "JSON good"); - - good = true; - Task t7; - try {t7 = Task (R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})");} - catch (const std::string& e){test.diag (e); good = false;} - test.ok (good, "Task::Task ('{two tags}')"); - test.is (t7.composeF4 (), R"([description:"DESC" entry:"1370212800" tags:"tag1,tag2"])", "F4 good"); - test.is (t7.composeJSON (), R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})", "JSON good"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/test/t_test.cpp b/test/t_test.cpp new file mode 100644 index 000000000..f953914fd --- /dev/null +++ b/test/t_test.cpp @@ -0,0 +1,170 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include "Context.h" + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest test(48); + Context context; + Context::setContext(&context); + + // Ensure environment has no influence. + unsetenv("TASKDATA"); + unsetenv("TASKRC"); + + test.is((int)Task::textToStatus("pending"), (int)Task::pending, "textToStatus pending"); + test.is((int)Task::textToStatus("completed"), (int)Task::completed, "textToStatus completed"); + test.is((int)Task::textToStatus("deleted"), (int)Task::deleted, "textToStatus deleted"); + test.is((int)Task::textToStatus("recurring"), (int)Task::recurring, "textToStatus recurring"); + + test.is(Task::statusToText(Task::pending), "pending", "statusToText pending"); + test.is(Task::statusToText(Task::completed), "completed", "statusToText completed"); + test.is(Task::statusToText(Task::deleted), "deleted", "statusToText deleted"); + test.is(Task::statusToText(Task::recurring), "recurring", "statusToText recurring"); + + /* + + TODO Task::composeCSV + TODO Task::composeYAML + TODO Task::id + TODO Task::*Status + TODO Task::*Tag* + TODO Task::*Annotation* + + TODO Task::addDependency + TODO Task::addDependency + TODO Task::removeDependency + TODO Task::removeDependency + TODO Task::getDependencies + TODO Task::getDependencies + + TODO Task::urgency + + TODO Task::encode + TODO Task::decode + + */ + + // Task::operator== + Task left("{\"one\":\"1\", \"two\":\"2\", \"three\":\"3\"}"); + Task right(left); + test.ok(left == right, "left == right -> true"); + left.set("one", "1.0"); + test.notok(left == right, "left == right -> false"); + + //////////////////////////////////////////////////////////////////////////////// + Task task; + + // Task::set + task = Task(); + task.set("name", "value"); + test.is(task.composeJSON(), "{\"name\":\"value\"}", "Task::set"); + + // Task::has + test.ok(task.has("name"), "Task::has"); + test.notok(task.has("woof"), "Task::has not"); + + // Task::get_int + task.set("one", 1); + test.is(task.composeJSON(), R"({"name":"value","one":"1"})", "Task::set"); + test.is(task.get_int("one"), 1, "Task::get_int"); + + // Task::get_ulong + task.set("two", "4294967295"); + test.is(task.composeJSON(), R"({"name":"value","one":"1","two":"4294967295"})", "Task::set"); + test.is((size_t)task.get_ulong("two"), (size_t)4294967295UL, "Task::get_ulong"); + + // Task::remove + task.remove("one"); + task.remove("two"); + test.is(task.composeJSON(), "{\"name\":\"value\"}", "Task::remove"); + + // Task::all + test.is(task.all().size(), (size_t)1, "Task::all size"); + + //////////////////////////////////////////////////////////////////////////////// + + Task::attributes["description"] = "string"; + Task::attributes["entry"] = "date"; + Task::attributes["tags"] = "string"; + Task::attributes["uuid"] = "string"; + + bool good = true; + try { + Task t4("{}"); + } catch (const std::string& e) { + test.diag(e); + good = false; + } + test.ok(good, "Task::Task ('{}')"); + + good = true; + try { + Task t5( + R"({"uuid":"00000000-0000-0000-000000000001","description":"foo","entry":"1234567890"})"); + } catch (const std::string& e) { + test.diag(e); + good = false; + } + test.ok(good, "Task::Task ('{}')"); + + // Verify tag handling is correct + Task t6; + t6.set("entry", "20130602T224000Z"); + t6.set("description", "DESC"); + t6.addTag("tag1"); + test.is(t6.composeJSON(), R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1"]})", + "JSON good"); + + t6.addTag("tag2"); + test.is(t6.composeJSON(), + R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})", + "JSON good"); + + good = true; + Task t7; + try { + t7 = Task(R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})"); + } catch (const std::string& e) { + test.diag(e); + good = false; + } + test.ok(good, "Task::Task ('{two tags}')"); + test.is(t7.composeJSON(), + R"({"description":"DESC","entry":"20130602T224000Z","tags":["tag1","tag2"]})", + "JSON good"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/tag.t b/test/tag.test.py similarity index 95% rename from test/tag.t rename to test/tag.test.py index a00df163d..32e4e0163 100755 --- a/test/tag.t +++ b/test/tag.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,15 +42,13 @@ class TestTags(TestCase): self.t = Task() def split_tags(self, tags): - return sorted(tags.strip().split(',')) + return sorted(tags.strip().split(",")) def test_tag_manipulation(self): """Test addition and removal of tags""" self.t("add +one This +two is a test +three") code, out, err = self.t("_get 1.tags") - self.assertEqual( - sorted(["one", "two", "three"]), - self.split_tags(out)) + self.assertEqual(sorted(["one", "two", "three"]), self.split_tags(out)) # Remove tags. self.t("1 modify -three -two -one") @@ -60,9 +58,7 @@ class TestTags(TestCase): # Add tags. self.t("1 modify +four +five +six") code, out, err = self.t("_get 1.tags") - self.assertEqual( - sorted(["four", "five", "six"]), - self.split_tags(out)) + self.assertEqual(sorted(["four", "five", "six"]), self.split_tags(out)) # Remove tags. self.t("1 modify -four -five -six") @@ -82,9 +78,7 @@ class TestTags(TestCase): """2655: Test bulk removal of tags""" self.t("add +one This +two is a test +three") code, out, err = self.t("_get 1.tags") - self.assertEqual( - sorted(["one", "two", "three"]), - self.split_tags(out)) + self.assertEqual(sorted(["one", "two", "three"]), self.split_tags(out)) # Remove all tags in bulk self.t("1 modify tags:") @@ -433,7 +427,7 @@ class TestVirtualTagUDA(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() - self.t.config("uda.animal.type", "string") + self.t.config("uda.animal.type", "string") self.t.config("uda.animal.label", "Animal") self.t("add one animal:donkey") self.t("add two") @@ -449,7 +443,9 @@ class TestVirtualTagORPHAN(TestCase): def setUp(self): """Executed before each test in the class""" self.t = Task() - self.t("add one rc.uda.animal.type:string rc.uda.animal.label:Animal animal:donkey") + self.t( + "add one rc.uda.animal.type:string rc.uda.animal.label:Animal animal:donkey" + ) self.t("add two") def test_virtual_tag_ORPHAN(self): @@ -474,13 +470,13 @@ class Test285(TestCase): # due:1month - - - - - - - ? # due:1year - - - - - - - - - cls.t('add due_last_week due:-1week') - cls.t('add due_yesterday due:-1day') - cls.t('add due_earlier_today due:today') - cls.t('add due_later_today due:tomorrow') - cls.t('add due_three_days due:3days') - cls.t('add due_next_month due:1month') - cls.t('add due_next_year due:1year') + cls.t("add due_last_week due:-1week") + cls.t("add due_yesterday due:-1day") + cls.t("add due_earlier_today due:today") + cls.t("add due_later_today due:tomorrow") + cls.t("add due_three_days due:3days") + cls.t("add due_next_month due:1month") + cls.t("add due_next_year due:1year") def test_overdue(self): """285: +OVERDUE""" @@ -538,8 +534,8 @@ class TestListAllTags(TestCase): def test_list_all_tags(self): """Verify the 'tags' command obeys 'rc.list.all.tags' - Create a data set of two tasks, with unique tags, one - pending, one completed. + Create a data set of two tasks, with unique tags, one + pending, one completed. """ self.t("add +t1 one") self.t("add +t2 two") @@ -571,6 +567,7 @@ class TestBug1700(TestCase): self.assertNotIn("tag1", out) self.assertIn("tag2,tag3", out) + class TestBug818(TestCase): def setUp(self): """Executed before each test in the class""" @@ -600,6 +597,7 @@ class TestBug818(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/taskchampion.sqlite3 b/test/taskchampion.sqlite3 deleted file mode 100644 index 5eac79740..000000000 Binary files a/test/taskchampion.sqlite3 and /dev/null differ diff --git a/test/taskrc.t b/test/taskrc.test.py similarity index 96% rename from test/taskrc.t rename to test/taskrc.test.py index 985f1c5d7..868ad2382 100755 --- a/test/taskrc.t +++ b/test/taskrc.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,6 +40,7 @@ class TestTaskrc(TestCase): """Executed before each test in the class""" self.t = Task() + @unittest.skip("taskrc generation requires a tty - see #3751") def test_default_taskrc(self): """Verify that a default .taskrc is generated""" os.remove(self.t.taskrc) @@ -49,6 +50,7 @@ class TestTaskrc(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tc.t.cpp b/test/tc.t.cpp deleted file mode 100644 index 1d4c7e519..000000000 --- a/test/tc.t.cpp +++ /dev/null @@ -1,113 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022, Dustin J. Mitchell -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include "test.h" -#include "tc/Replica.h" -#include "tc/WorkingSet.h" -#include "tc/Task.h" -#include "tc/util.h" - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (23); - - // This function contains unit tests for the various bits of the wrappers for - // taskchampion-lib (that is, for `src/tc/*.cpp`). - - //// util - - { - auto s1 = std::string ("a\0string!"); - auto stc = tc::string2tc (s1); - auto s2 = tc::tc2string (stc); - t.is (s1, s2, "round-trip to tc string and back (containing an embedded NUL)"); - } - - { - auto s1 = std::string ("62123ec9-c443-4f7e-919a-35362a8bef8d"); - auto tcuuid = tc::uuid2tc (s1); - auto s2 = tc::tc2uuid (tcuuid); - t.is(s1, s2, "round-trip to TCUuid and back"); - } - - //// Replica - - auto rep = tc::Replica (); - t.pass ("replica constructed"); - - auto maybe_task = rep.get_task("24478a28-4609-4257-bc19-44ec51391431"); - t.notok(maybe_task.has_value(), "task with fixed uuid does not exist"); - - auto task = rep.new_task (tc::Status::Pending, "a test"); - t.pass ("new task constructed"); - t.is (task.get_description (), std::string ("a test"), "task description round-trip"); - t.is (task.get_status (), tc::Status::Pending, "task status round-trip"); - - auto uuid = task.get_uuid(); - - auto maybe_task2 = rep.get_task (uuid); - t.ok(maybe_task2.has_value(), "task lookup by uuid finds task"); - t.is ((*maybe_task2).get_description (), std::string ("a test"), "task description round-trip"); - - rep.rebuild_working_set (true); - t.pass ("rebuild_working_set"); - - auto tasks = rep.all_tasks (); - t.is ((int)tasks.size(), 1, "all_tasks returns one task"); - - //// Task - - task = std::move(tasks[0]); - - t.is (task.get_uuid(), uuid, "returned task has correct uuid"); - t.is (task.get_status(), tc::Status::Pending, "returned task is pending"); - auto map = task.get_taskmap (); - t.is (map["description"], "a test", "task description in taskmap"); - t.is (task.get_description(), "a test", "returned task has correct description"); - t.is (task.is_waiting(), false, "task is not waiting"); - t.is (task.is_active(), false, "task is not active"); - - //// WorkingSet - - auto ws = rep.working_set (); - - t.is (ws.len (), (size_t)1, "WorkingSet::len"); - t.is (ws.largest_index (), (size_t)1, "WorkingSet::largest_index"); - t.is (ws.by_index (1).value(), uuid, "WorkingSet::by_index"); - t.is (ws.by_index (2).has_value(), false, "WorkingSet::by_index for unknown index"); - t.is (ws.by_uuid (uuid).value (), (size_t)1, "WorkingSet::by_uuid"); - t.is (ws.by_uuid ("3e18a306-e3a8-4a53-a85c-fa7c057759a2").has_value (), false, "WorkingSet::by_uuid for unknown uuid"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/test/tc_cpp_test.cpp b/test/tc_cpp_test.cpp new file mode 100644 index 000000000..c92e5e66d --- /dev/null +++ b/test/tc_cpp_test.cpp @@ -0,0 +1,157 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022, Dustin J. Mitchell +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include + +#include + +std::string uuid2str(tc::Uuid uuid) { return static_cast(uuid.to_string()); } + +//////////////////////////////////////////////////////////////////////////////// +// Tests for the basic cxxbridge functionality. This focuses on the methods with +// complex cxxbridge implementations, rather than those with complex Rust +// implementations but simple APIs, like sync. +int TEST_NAME(int, char **) { + UnitTest t; + std::string str; + + auto replica = tc::new_replica_in_memory(); + auto uuid = tc::uuid_v4(); + auto uuid2 = tc::uuid_v4(); + t.is(uuid2str(uuid).size(), (size_t)36, "uuid string is the right length"); + + rust::Vec ops; + auto task = tc::create_task(uuid, ops); + t.is(uuid2str(task->get_uuid()), uuid2str(uuid), "new task has correct uuid"); + task->update("status", "pending", ops); + task->update("description", "a task", ops); + task->update("description", "a cool task", ops); + tc::add_undo_point(ops); + task->delete_task(ops); + + t.is(ops[0].is_create(), true, "ops[0] is create"); + t.is(uuid2str(ops[0].get_uuid()), uuid2str(uuid), "ops[0] has correct uuid"); + + t.is(ops[1].is_update(), true, "ops[1] is update"); + t.is(uuid2str(ops[1].get_uuid()), uuid2str(uuid), "ops[1] has correct uuid"); + ops[1].get_property(str); + t.is(str, "status", "ops[1] property is 'status'"); + t.ok(ops[1].get_value(str), "get_value succeeds"); + t.is(str, "pending", "ops[1] value is 'pending'"); + t.ok(!ops[1].get_old_value(str), "get_old_value has no old value"); + + t.is(ops[2].is_update(), true, "ops[2] is update"); + t.is(uuid2str(ops[2].get_uuid()), uuid2str(uuid), "ops[2] has correct uuid"); + ops[2].get_property(str); + t.is(str, "description", "ops[2] property is 'description'"); + t.ok(ops[2].get_value(str), "get_value succeeds"); + t.is(str, "a task", "ops[2] value is 'a task'"); + t.ok(!ops[2].get_old_value(str), "get_old_value has no old value"); + + t.is(ops[3].is_update(), true, "ops[3] is update"); + t.is(uuid2str(ops[3].get_uuid()), uuid2str(uuid), "ops[3] has correct uuid"); + ops[3].get_property(str); + t.is(str, "description", "ops[3] property is 'description'"); + t.ok(ops[3].get_value(str), "get_value succeeds"); + t.is(str, "a cool task", "ops[3] value is 'a cool task'"); + t.ok(ops[3].get_old_value(str), "get_old_value succeeds"); + t.is(str, "a task", "ops[3] old value is 'a task'"); + + t.is(ops[4].is_undo_point(), true, "ops[4] is undo_point"); + + t.is(ops[5].is_delete(), true, "ops[5] is delete"); + t.is(uuid2str(ops[5].get_uuid()), uuid2str(uuid), "ops[5] has correct uuid"); + auto old_task = ops[5].get_old_task(); + // old_task is in arbitrary order, so just check that status is in there. + bool found = false; + for (auto &pv : old_task) { + std::string p = static_cast(pv.prop); + if (p == "status") { + std::string v = static_cast(pv.value); + t.is(v, "pending", "old_task has status:pending"); + found = true; + } + } + t.ok(found, "found the status property in ops[5].old_task"); + + replica->commit_operations(std::move(ops)); + auto maybe_task2 = replica->get_task_data(tc::uuid_v4()); + t.ok(maybe_task2.is_none(), "looking up a random uuid gets nothing"); + + // The last operation deleted the task, but we want to see the task, so undo it.. + auto undo_ops = replica->get_undo_operations(); + t.ok(replica->commit_reversed_operations(std::move(undo_ops)), "undo committed successfully"); + + auto maybe_task3 = replica->get_task_data(uuid); + t.ok(maybe_task3.is_some(), "looking up the original uuid get TaskData"); + rust::Box task3 = maybe_task3.take(); + t.is(uuid2str(task3->get_uuid()), uuid2str(uuid), "reloaded task has correct uuid"); + t.ok(task3->get("description", str), "reloaded task has a description"); + t.is(str, "a cool task", "reloaded task has correct description"); + t.ok(task3->get("status", str), "reloaded task has a status"); + t.is(str, "pending", "reloaded task has correct status"); + + t.is(task3->properties().size(), (size_t)2, "task has 2 properties"); + t.is(task3->items().size(), (size_t)2, "task has 2 items"); + + rust::Vec ops2; + auto task4 = tc::create_task(uuid2, ops2); + task4->update("description", "another", ops2); + replica->commit_operations(std::move(ops2)); + + auto all_tasks = replica->all_task_data(); + t.is(all_tasks.size(), (size_t)2, "now there are 2 tasks"); + for (auto &maybe_task : all_tasks) { + t.ok(maybe_task.is_some(), "all_tasks is fully populated"); + auto task = maybe_task.take(); + if (task->get_uuid() == uuid) { + t.ok(task->get("description", str), "get_value succeeds"); + t.is(str, "a cool task", "description is 'a cool task'"); + } + } + + // Check exception formatting. + try { + replica->sync_to_local("/does/not/exist", false); + // tc::new_replica_on_disk("/does/not/exist", false); + } catch (rust::Error &err) { + t.is(err.what(), + "unable to open database file: /does/not/exist/taskchampion-local-sync-server.sqlite3: " + "Error code 14: Unable to open the database file", + "error message has full context"); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/tdb2.t.cpp b/test/tdb2.t.cpp deleted file mode 100644 index 398a4b130..000000000 --- a/test/tdb2.t.cpp +++ /dev/null @@ -1,133 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include - -Context context; - -void cleardb () -{ - // Remove any residual test files. - rmdir ("./extensions"); - unlink ("./pending.data"); - unlink ("./completed.data"); - unlink ("./undo.data"); - unlink ("./backlog.data"); -} - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (12); - - // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); - - try - { - cleardb (); - - // Set the context to allow GC. - context.config.set ("gc", 1); - context.config.set ("debug", 1); - - context.tdb2.open_replica (".", true); - - // Try reading an empty database. - std::vector pending = context.tdb2.pending_tasks (); - std::vector completed = context.tdb2.completed_tasks (); - int num_reverts_possible = context.tdb2.num_reverts_possible (); - int num_local_changes = context.tdb2.num_local_changes (); - - t.is ((int) pending.size (), 0, "TDB2 Read empty pending"); - t.is ((int) completed.size (), 0, "TDB2 Read empty completed"); - t.is ((int) num_reverts_possible, 0, "TDB2 Read empty undo"); - t.is ((int) num_local_changes, 0, "TDB2 Read empty backlog"); - - // Add a task. - Task task (R"([description:"description" name:"value"])"); - context.tdb2.add (task); - - pending = context.tdb2.pending_tasks (); - completed = context.tdb2.completed_tasks (); - num_reverts_possible = context.tdb2.num_reverts_possible (); - num_local_changes = context.tdb2.num_local_changes (); - - t.is ((int) pending.size (), 1, "TDB2 after add, 1 pending task"); - t.is ((int) completed.size (), 0, "TDB2 after add, 0 completed tasks"); - t.is ((int) num_reverts_possible, 3, "TDB2 after add, 3 undo lines"); - t.is ((int) num_local_changes, 1, "TDB2 after add, 1 backlog task"); - - task.set ("description", "This is a test"); - context.tdb2.modify (task); - - pending = context.tdb2.pending_tasks (); - completed = context.tdb2.completed_tasks (); - num_reverts_possible = context.tdb2.num_reverts_possible (); - num_local_changes = context.tdb2.num_local_changes (); - - t.is ((int) pending.size (), 1, "TDB2 after add, 1 pending task"); - t.is ((int) completed.size (), 0, "TDB2 after add, 0 completed tasks"); - t.is ((int) num_reverts_possible, 7, "TDB2 after add, 7 undo lines"); - t.is ((int) num_local_changes, 2, "TDB2 after add, 2 backlog task"); - - // Reset for reuse. - cleardb (); - context.tdb2.open_replica (".", true); - - // TODO complete a task - // TODO gc - } - - catch (const std::string& error) - { - t.diag (error); - return -1; - } - - catch (...) - { - t.diag ("Unknown error."); - return -2; - } - - rmdir ("./extensions"); - unlink ("./pending.data"); - unlink ("./completed.data"); - unlink ("./undo.data"); - unlink ("./backlog.data"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/test/tdb2_test.cpp b/test/tdb2_test.cpp new file mode 100644 index 000000000..f1baf2444 --- /dev/null +++ b/test/tdb2_test.cpp @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include "Context.h" + +namespace { + +void cleardb() { + // Remove any residual test files. + rmdir("./extensions"); + unlink("./taskchampion.sqlite3"); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(12); + Context context; + Context::setContext(&context); + + // Ensure environment has no influence. + unsetenv("TASKDATA"); + unsetenv("TASKRC"); + + try { + cleardb(); + + // Set the context to allow GC. + context.config.set("gc", 1); + context.config.set("debug", 1); + + context.tdb2.open_replica(".", /*create_if_missing=*/true, /*read_write=*/true); + + // Try reading an empty database. + std::vector pending = context.tdb2.pending_tasks(); + std::vector completed = context.tdb2.completed_tasks(); + int num_reverts_possible = context.tdb2.num_reverts_possible(); + int num_local_changes = context.tdb2.num_local_changes(); + + t.is((int)pending.size(), 0, "TDB2 Read empty pending"); + t.is((int)completed.size(), 0, "TDB2 Read empty completed"); + t.is((int)num_reverts_possible, 0, "TDB2 Read empty undo"); + t.is((int)num_local_changes, 0, "TDB2 Read empty backlog"); + + // Add a task. + Task task(R"([description:"description" name:"value"])"); + context.tdb2.add(task); + + pending = context.tdb2.pending_tasks(); + completed = context.tdb2.completed_tasks(); + num_reverts_possible = context.tdb2.num_reverts_possible(); + num_local_changes = context.tdb2.num_local_changes(); + + t.is((int)pending.size(), 1, "TDB2 after add, 1 pending task"); + t.is((int)completed.size(), 0, "TDB2 after add, 0 completed tasks"); + t.is((int)num_reverts_possible, 1, "TDB2 after add, 1 revert possible"); + t.is((int)num_local_changes, 6, "TDB2 after add, 6 local changes"); + + task.set("description", "This is a test"); + context.tdb2.modify(task); + + pending = context.tdb2.pending_tasks(); + completed = context.tdb2.completed_tasks(); + num_reverts_possible = context.tdb2.num_reverts_possible(); + num_local_changes = context.tdb2.num_local_changes(); + + t.is((int)pending.size(), 1, "TDB2 after set, 1 pending task"); + t.is((int)completed.size(), 0, "TDB2 after set, 0 completed tasks"); + t.is((int)num_reverts_possible, 1, "TDB2 after set, 1 revert possible"); + + // At this point, there may be 7 or 8 local changes, depending on whether + // the `modified` property changed between the `add` and `modify` + // invocation. That only happens if the clock ticks over to the next second + // between those invocations. + t.ok(num_local_changes == 7 || num_local_changes == 8, "TDB2 after set, 7 or 8 local changes"); + + // Reset for reuse. + cleardb(); + context.tdb2.open_replica(".", /*create_if_missing=*/true, /*read_write=*/true); + + // TODO complete a task + // TODO gc + } + + catch (const std::string& error) { + t.diag(error); + return -1; + } + + catch (...) { + t.diag("Unknown error."); + return -2; + } + + rmdir("./extensions"); + unlink("./pending.data"); + unlink("./completed.data"); + unlink("./undo.data"); + unlink("./backlog.data"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/template.t b/test/template.test.py old mode 100644 new mode 100755 similarity index 88% rename from test/template.t rename to test/template.test.py index a41273f8d..ed6450d59 --- a/test/template.t +++ b/test/template.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest from datetime import datetime + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -54,6 +54,7 @@ from basetest import Task, TestCase # self.assertNotRegex(t, r) # self.tap("") + class TestBugNumber(TestCase): @classmethod def setUpClass(cls): @@ -72,7 +73,7 @@ class TestBugNumber(TestCase): """Copyright is current""" code, out, err = self.t("version") - expected = "Copyright \(C\) \d{4} - %d" % (datetime.now().year,) + expected = r"Copyright \(C\) \d{4} - %d" % (datetime.now().year,) self.assertRegex(out.decode("utf8"), expected) # TAP diagnostics on the bas @@ -81,16 +82,16 @@ class TestBugNumber(TestCase): def test_faketime(self): """Running tests using libfaketime - WARNING: - faketime version 0.9.6 and later correctly propagates non-zero - exit codes. Please don't combine faketime tests and - self.t.runError(). + WARNING: + faketime version 0.9.6 and later correctly propagates non-zero + exit codes. Please don't combine faketime tests and + self.t.runError(). - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=750721 + https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=750721 """ self.t.faketime("-2y") - command = ("add Testing") + command = "add Testing" self.t(command) # Remove FAKETIME settings @@ -189,12 +190,12 @@ sys.exit(0) self.assertIn("/Hello/Greetings/", logs["calls"][0]["args"]) # Some message output from the hook - self.assertEqual(logs["output"]["msgs"][0], - "Hello from the template hook") + self.assertEqual(logs["output"]["msgs"][0], "Hello from the template hook") # This is what taskwarrior received - self.assertEqual(logs["output"]["json"][0]["description"], - "This is an example modify hook") + self.assertEqual( + logs["output"]["json"][0]["description"], "This is an example modify hook" + ) def test_onmodify_bad_builtin_with_log(self): """Testing a builtin hook and keeping track of its input/output @@ -217,16 +218,17 @@ sys.exit(0) hook.assertExitcode(1) # Some message output from the hook - self.assertEqual(logs["output"]["msgs"][0], - "Hello from the template hook") + self.assertEqual(logs["output"]["msgs"][0], "Hello from the template hook") # This is what taskwarrior would have used if hook finished cleanly - self.assertEqual(logs["output"]["json"][0]["description"], - "This is an example modify hook") + self.assertEqual( + logs["output"]["json"][0]["description"], "This is an example modify hook" + ) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/test.cpp b/test/test.cpp index 16a7fb176..32f62c4b7 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -24,75 +24,46 @@ // //////////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include #include +#include #include +#include #include +#include + +#include +#include /////////////////////////////////////////////////////////////////////////////// -UnitTest::UnitTest () -: _planned (0) -, _counter (0) -, _passed (0) -, _failed (0) -, _skipped (0) -{ -} +UnitTest::UnitTest() : _planned(0), _counter(0), _passed(0), _failed(0), _skipped(0) {} /////////////////////////////////////////////////////////////////////////////// -UnitTest::UnitTest (int planned) -: _planned (planned) -, _counter (0) -, _passed (0) -, _failed (0) -, _skipped (0) -{ +UnitTest::UnitTest(int planned) + : _planned(planned), _counter(0), _passed(0), _failed(0), _skipped(0) { std::cout << "1.." << _planned << '\n'; } /////////////////////////////////////////////////////////////////////////////// -UnitTest::~UnitTest () -{ +UnitTest::~UnitTest() { float percentPassed = 0.0; if (_planned > 0) - percentPassed = (100.0 * _passed) / std::max (_planned, _passed + _failed + _skipped); + percentPassed = (100.0 * _passed) / std::max(_planned, _passed + _failed + _skipped); - if (_counter < _planned) - { - std::cout << "# Only " - << _counter - << " tests, out of a planned " - << _planned - << " were run.\n"; + if (_counter < _planned) { + std::cout << "# Only " << _counter << " tests, out of a planned " << _planned << " were run.\n"; _skipped += _planned - _counter; } else if (_counter > _planned) - std::cout << "# " - << _counter - << " tests were run, but only " - << _planned - << " were planned.\n"; + std::cout << "# " << _counter << " tests were run, but only " << _planned << " were planned.\n"; - std::cout << "# " - << _passed - << " passed, " - << _failed - << " failed, " - << _skipped - << " skipped. " - << std::setprecision (3) << percentPassed - << "% passed.\n"; - exit (_failed > 0); + std::cout << "# " << _passed << " passed, " << _failed << " failed, " << _skipped << " skipped. " + << std::setprecision(3) << percentPassed << "% passed.\n"; + exit(_failed > 0); } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::plan (int planned) -{ +void UnitTest::plan(int planned) { _planned = planned; _counter = 0; _passed = 0; @@ -103,419 +74,225 @@ void UnitTest::plan (int planned) } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::planMore (int extra) -{ +void UnitTest::planMore(int extra) { _planned += extra; std::cout << "1.." << _planned << '\n'; } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::ok (bool expression, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::ok(bool expression, const std::string& name, bool expfail /* = false */) { ++_counter; bool success = expression; - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::notok (bool expression, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::notok(bool expression, const std::string& name, bool expfail /* = false */) { ++_counter; bool success = not expression; - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (bool actual, bool expected, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(bool actual, bool expected, const std::string& name, bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (size_t actual, size_t expected, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(size_t actual, size_t expected, const std::string& name, + bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (int actual, int expected, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(int actual, int expected, const std::string& name, bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (double actual, double expected, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(double actual, double expected, const std::string& name, + bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (double actual, double expected, double tolerance, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(double actual, double expected, double tolerance, const std::string& name, + bool expfail /* = false */) { ++_counter; - bool success = (fabs (actual - expected) <= tolerance); + bool success = (fabs(actual - expected) <= tolerance); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is (unsigned char actual, unsigned char expected, const std::string& name, bool expfail /* = false */) -{ +void UnitTest::is(unsigned char actual, unsigned char expected, const std::string& name, + bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: " - << expected - << "\n# got: " - << actual - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: " << expected + << "\n# got: " << actual << '\n'; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is ( - const std::string& actual, - const std::string& expected, - const std::string& name, - bool expfail /* = false */) -{ +void UnitTest::is(const std::string& actual, const std::string& expected, const std::string& name, + bool expfail /* = false */) { ++_counter; bool success = (actual == expected); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: '" - << expected - << "'" - << "\n# got: '" - << actual - << "'\n"; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: '" + << expected << "'" + << "\n# got: '" << actual << "'\n"; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::is ( - const char* actual, - const char* expected, - const std::string& name, - bool expfail /* = false */) -{ +void UnitTest::is(const char* actual, const char* expected, const std::string& name, + bool expfail /* = false */) { ++_counter; - bool success = (! strcmp (actual, expected)); + bool success = (!strcmp(actual, expected)); - if (success and ! expfail) - { + if (success and !expfail) { ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << name - << '\n'; - } - else - { - ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << name - << (expfail ? (success ? " # FIXED" : " # TODO") : "") - << "\n# expected: '" - << expected - << "'" - << "\n# got: '" - << actual - << "'\n"; + std::cout << green("ok") << " " << _counter << " - " << name << '\n'; + } else { + if (success == expfail) ++_failed; + std::cout << red("not ok") << " " << _counter << " - " << name + << (expfail ? (success ? " # FIXED" : " # TODO") : "") << "\n# expected: '" + << expected << "'" + << "\n# got: '" << actual << "'\n"; } } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::diag (const std::string& text) -{ - auto start = text.find_first_not_of (" \t\n\r\f"); - auto end = text.find_last_not_of (" \t\n\r\f"); - if (start != std::string::npos && - end != std::string::npos) - std::cout << "# " << text.substr (start, end - start + 1) << '\n'; +void UnitTest::diag(const std::string& text) { + auto start = text.find_first_not_of(" \t\n\r\f"); + auto end = text.find_last_not_of(" \t\n\r\f"); + if (start != std::string::npos && end != std::string::npos) + std::cout << "# " << text.substr(start, end - start + 1) << '\n'; } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::pass (const std::string& text) -{ +void UnitTest::pass(const std::string& text) { ++_counter; ++_passed; - std::cout << green ("ok") - << " " - << _counter - << " - " - << text - << '\n'; + std::cout << green("ok") << " " << _counter << " - " << text << '\n'; } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::fail (const std::string& text) -{ +void UnitTest::fail(const std::string& text) { ++_counter; ++_failed; - std::cout << red ("not ok") - << " " - << _counter - << " - " - << text - << '\n'; + std::cout << red("not ok") << " " << _counter << " - " << text << '\n'; } /////////////////////////////////////////////////////////////////////////////// -void UnitTest::skip (const std::string& text) -{ +void UnitTest::skip(const std::string& text) { ++_counter; ++_skipped; - std::cout << yellow ("ok") - << " " - << _counter - << " - " - << text - << " # skip" - << '\n'; + std::cout << yellow("ok") << " " << _counter << " - " << text << " # skip" << '\n'; } /////////////////////////////////////////////////////////////////////////////// -std::string UnitTest::red (const std::string& input) -{ - if (isatty (fileno (stdout))) - return std::string ("\033[31m" + input + "\033[0m"); +std::string UnitTest::red(const std::string& input) { + if (isatty(fileno(stdout))) return std::string("\033[31m" + input + "\033[0m"); return input; } /////////////////////////////////////////////////////////////////////////////// -std::string UnitTest::green (const std::string& input) -{ - if (isatty (fileno (stdout))) - return std::string ("\033[32m" + input + "\033[0m"); +std::string UnitTest::green(const std::string& input) { + if (isatty(fileno(stdout))) return std::string("\033[32m" + input + "\033[0m"); return input; } /////////////////////////////////////////////////////////////////////////////// -std::string UnitTest::yellow (const std::string& input) -{ - if (isatty (fileno (stdout))) - return std::string ("\033[33m" + input + "\033[0m"); +std::string UnitTest::yellow(const std::string& input) { + if (isatty(fileno(stdout))) return std::string("\033[33m" + input + "\033[0m"); return input; } diff --git a/test/test.h b/test/test.h index 21783f188..0c27e8e85 100644 --- a/test/test.h +++ b/test/test.h @@ -29,36 +29,35 @@ #include -class UnitTest -{ -public: - UnitTest (); - UnitTest (int); - ~UnitTest (); +class UnitTest { + public: + UnitTest(); + UnitTest(int); + ~UnitTest(); - void plan (int); - void planMore (int); - void ok (bool, const std::string&, bool expfail = false); - void notok (bool, const std::string&, bool expfail = false); - void is (bool, bool, const std::string&, bool expfail = false); - void is (size_t, size_t, const std::string&, bool expfail = false); - void is (int, int, const std::string&, bool expfail = false); - void is (double, double, const std::string&, bool expfail = false); - void is (double, double, double, const std::string&, bool expfail = false); - void is (unsigned char, unsigned char, const std::string&, bool expfail = false); - void is (const std::string&, const std::string&, const std::string&, bool expfail = false); - void is (const char*, const char*, const std::string&, bool expfail = false); - void diag (const std::string&); - void pass (const std::string&); - void fail (const std::string&); - void skip (const std::string&); + void plan(int); + void planMore(int); + void ok(bool, const std::string&, bool expfail = false); + void notok(bool, const std::string&, bool expfail = false); + void is(bool, bool, const std::string&, bool expfail = false); + void is(size_t, size_t, const std::string&, bool expfail = false); + void is(int, int, const std::string&, bool expfail = false); + void is(double, double, const std::string&, bool expfail = false); + void is(double, double, double, const std::string&, bool expfail = false); + void is(unsigned char, unsigned char, const std::string&, bool expfail = false); + void is(const std::string&, const std::string&, const std::string&, bool expfail = false); + void is(const char*, const char*, const std::string&, bool expfail = false); + void diag(const std::string&); + void pass(const std::string&); + void fail(const std::string&); + void skip(const std::string&); -private: - std::string red (const std::string&); - std::string green (const std::string&); - std::string yellow (const std::string&); + private: + std::string red(const std::string&); + std::string green(const std::string&); + std::string yellow(const std::string&); -private: + private: int _planned; int _counter; int _passed; diff --git a/test/test_certs/README b/test/test_certs/README deleted file mode 100644 index 8623d31c6..000000000 --- a/test/test_certs/README +++ /dev/null @@ -1,5 +0,0 @@ -WARNING - -These certificates are part of the testing framework, DO NOT USE FOR OTHER PURPOSES - -WARNING diff --git a/test/test_certs/api.cert.pem b/test/test_certs/api.cert.pem deleted file mode 100644 index 85331f205..000000000 --- a/test/test_certs/api.cert.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIErDCCAxSgAwIBAgIUImT6dvD09I4yGowOKIyYCFwCU/IwDQYJKoZIhvcNAQEL -BQAwdDEVMBMGA1UEAxMMbG9jYWxob3N0IENBMR4wHAYDVQQKDBVHw7Z0ZWJvcmcg -Qml0IEZhY3RvcnkxEjAQBgNVBAcMCUfDtnRlYm9yZzEaMBgGA1UECAwRVsOkc3Ry -YSBHw7Z0YWxhbmQxCzAJBgNVBAYTAlNFMB4XDTE5MDMwMzE1MDAzOVoXDTIwMDMw -MjE1MDAzOVowNDESMBAGA1UEAxMJbG9jYWxob3N0MR4wHAYDVQQKDBVHw7Z0ZWJv -cmcgQml0IEZhY3RvcnkwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDm -SHBv3zILItaJrG+3y7dWgvsX6X3FzLVXOrhFOBNbsrHSsktt20yHZhofoelkgtlc -kBsft5GUYGEBZuJIIFcOvpbXXTz/cYge8WhpcFo64APNc7zxykaTeIHMmEqjxqtO -efyOmuEyWya2XbCA4aZ6emyirkem952wBJNYLIWoy53YQ79dFvH2SOWKPlXCa1B/ -ih7b9qYZE2XHoLQSbLCJqDWoQTxmOe13/1aKnJIuIy/igGsM7ygmphG4kzvyfbiA -o+Nd50USVeeeF8P+X2YZfXFKmbzJTg4q6UUB4vqn4XegW065eIyN1Xw3Z/l8CiP0 -G27iuk/ZfRuz00+d9oTG5OtvyCMcc/kDI4Db1Xu9C5z9UpF5IIM85Ky91YxMMqPu -ChQQoDXJ7mdtC7zRvn6t0erMVbpaqyXxKll/ybPFAZYIgV7jtSPPJDAcTsCZQLRc -VcggScYSiWkRgW+DKsECczo++6577q++cyRBtENPOXUZ6J74eKBHizYda0chVzcC -AwEAAaN2MHQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDAjAPBgNV -HQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBQuQdj0wAQOzML5AQ13xsb4E8Vh9DAfBgNV -HSMEGDAWgBSmHbkkwFmCruE7I7TSI0SlD1hLiDANBgkqhkiG9w0BAQsFAAOCAYEA -CD77JzLXB0YbgJrrkotw0I8rmrb8mw0tHkDGH70UyG6VhEt3vaYcYioxs3TIo23U -dhOTApGdHzjrZePa0y2Kh7YtnPLAUml6hHTATE1D7Hufdtamd56URgBrEylKkpKl -6UnwHWzlkdRWzPRo+RD9RMBzTUMiooZaP9zzzYQNEfCmMTlHqztOLPNfFR6kFLr5 -Xnc04EV/hvkByoBmo9/i4qAa3AW4y6cUHYpxVS9nBF2h364aDIIA0mn9hEewRJBq -3odng4d+e9v9b+G1ExQXM3en39REo/b73P2VATZyMxmq5gxL1OmbOa13A+S2D0Og -OkqNq6vzRhTxLbkqc0fjKv2TUFrnYCsCROvrMF+TAN0mG4NcfgeeziBTmHJq0D3I -LNTRMU/BoMYG3wGu0BUak83a9JhwViR3o0iFnctuMbD2Mz6QOawgWz8BuTcwIur6 -USeERs3+G2XX499wWswGgWK5JGZ1skD1PiRsZfuaCMJHBiWolv4/G+My5IYg6B8a ------END CERTIFICATE----- diff --git a/test/test_certs/api.key.pem b/test/test_certs/api.key.pem deleted file mode 100644 index b294f3e3f..000000000 --- a/test/test_certs/api.key.pem +++ /dev/null @@ -1,182 +0,0 @@ -Public Key Info: - Public Key Algorithm: RSA - Key Security Level: High (3072 bits) - -modulus: - 00:e6:48:70:6f:df:32:0b:22:d6:89:ac:6f:b7:cb:b7 - 56:82:fb:17:e9:7d:c5:cc:b5:57:3a:b8:45:38:13:5b - b2:b1:d2:b2:4b:6d:db:4c:87:66:1a:1f:a1:e9:64:82 - d9:5c:90:1b:1f:b7:91:94:60:61:01:66:e2:48:20:57 - 0e:be:96:d7:5d:3c:ff:71:88:1e:f1:68:69:70:5a:3a - e0:03:cd:73:bc:f1:ca:46:93:78:81:cc:98:4a:a3:c6 - ab:4e:79:fc:8e:9a:e1:32:5b:26:b6:5d:b0:80:e1:a6 - 7a:7a:6c:a2:ae:47:a6:f7:9d:b0:04:93:58:2c:85:a8 - cb:9d:d8:43:bf:5d:16:f1:f6:48:e5:8a:3e:55:c2:6b - 50:7f:8a:1e:db:f6:a6:19:13:65:c7:a0:b4:12:6c:b0 - 89:a8:35:a8:41:3c:66:39:ed:77:ff:56:8a:9c:92:2e - 23:2f:e2:80:6b:0c:ef:28:26:a6:11:b8:93:3b:f2:7d - b8:80:a3:e3:5d:e7:45:12:55:e7:9e:17:c3:fe:5f:66 - 19:7d:71:4a:99:bc:c9:4e:0e:2a:e9:45:01:e2:fa:a7 - e1:77:a0:5b:4e:b9:78:8c:8d:d5:7c:37:67:f9:7c:0a - 23:f4:1b:6e:e2:ba:4f:d9:7d:1b:b3:d3:4f:9d:f6:84 - c6:e4:eb:6f:c8:23:1c:73:f9:03:23:80:db:d5:7b:bd - 0b:9c:fd:52:91:79:20:83:3c:e4:ac:bd:d5:8c:4c:32 - a3:ee:0a:14:10:a0:35:c9:ee:67:6d:0b:bc:d1:be:7e - ad:d1:ea:cc:55:ba:5a:ab:25:f1:2a:59:7f:c9:b3:c5 - 01:96:08:81:5e:e3:b5:23:cf:24:30:1c:4e:c0:99:40 - b4:5c:55:c8:20:49:c6:12:89:69:11:81:6f:83:2a:c1 - 02:73:3a:3e:fb:ae:7b:ee:af:be:73:24:41:b4:43:4f - 39:75:19:e8:9e:f8:78:a0:47:8b:36:1d:6b:47:21:57 - 37: - -public exponent: - 01:00:01: - -private exponent: - 00:8b:e2:40:fa:93:f8:10:2f:af:66:9d:ea:97:19:16 - 5b:64:e1:26:1b:5d:9d:43:c6:7c:20:5d:43:1e:d7:13 - 82:ae:e6:30:0c:05:c5:8a:ed:4c:a6:5d:c4:ba:c3:a5 - 80:67:eb:d9:ae:20:92:3c:31:77:7b:a4:85:9c:0e:99 - 13:89:ce:93:30:3e:17:65:5d:ac:7e:34:50:a8:41:07 - 36:80:d8:d2:8f:59:c8:e7:aa:39:2f:8f:9a:8a:ec:85 - 88:15:f9:9f:e2:f8:4e:07:8a:bb:2f:58:26:19:83:f8 - de:b9:73:38:36:e9:ab:91:0a:a6:9b:80:ed:b4:cd:d4 - 45:2b:b2:ed:24:57:65:d2:c1:2a:72:d4:d1:1c:c3:26 - f1:15:28:4f:aa:8a:5f:47:28:33:51:5a:5b:48:3d:e1 - d7:1c:e8:cb:36:25:7c:6b:7f:c6:be:c2:51:1c:de:e7 - 4b:d4:90:a0:35:66:fb:f7:c5:d2:67:3d:59:a2:b6:a0 - 8a:c6:03:8b:db:3a:39:32:28:21:ba:11:3b:a3:77:90 - 59:0d:72:81:01:ad:5f:66:92:b5:6c:61:d2:8c:60:eb - e5:62:8d:5c:71:72:99:7c:12:b8:1c:c6:ba:ee:64:17 - 41:04:b6:d2:59:f0:1a:0f:74:ca:d5:19:1e:98:f4:61 - 75:c3:e1:50:27:af:fa:48:87:3d:c9:11:aa:70:3e:56 - 64:a1:dc:42:d9:16:a6:d2:14:98:48:cd:ad:d2:09:33 - 00:10:5f:3c:69:36:81:96:75:44:f8:56:95:d2:e5:76 - 85:41:85:8d:f1:64:14:05:6b:f7:2a:26:6a:d9:f0:b9 - d0:de:d6:3d:7e:7c:6c:2c:26:49:22:5e:2a:04:21:4f - c9:d7:3c:a1:1b:7b:d1:27:1d:80:38:25:57:7f:a2:99 - 46:d7:60:56:e6:5d:50:71:36:b6:af:a6:5b:d0:92:d8 - 19:0e:86:4b:3b:34:cf:cd:ea:e5:35:e4:4e:65:e3:20 - 91: - -prime1: - 00:f7:07:aa:be:24:f1:2f:26:ea:0e:45:86:b0:45:05 - df:b8:59:d6:3b:40:5c:dd:c8:bf:18:3e:76:74:e8:31 - 35:ca:b0:d4:63:df:4d:4c:d5:0c:46:6e:31:71:3c:17 - 5e:45:4e:5c:a1:96:4f:69:5e:92:bd:e8:09:27:85:50 - e3:29:1d:bb:8e:f9:2c:41:af:0f:c8:e0:d3:70:6a:d4 - b9:67:43:08:e4:4a:c9:12:f6:d2:7e:7d:bc:69:52:ba - 48:96:0a:7e:42:e7:6e:82:e8:0c:6c:1b:a5:01:f0:36 - 2c:ae:a4:2f:d8:62:ef:ab:1c:47:3a:98:79:40:68:dd - de:6a:b4:8c:53:03:09:78:11:27:36:6f:e4:2b:fb:f4 - 4d:bf:13:30:37:1f:51:fd:2f:84:d9:b9:62:ea:91:a8 - e8:72:9d:78:14:bd:5a:9e:1f:06:12:70:19:bd:3b:80 - b2:5c:33:8b:d6:5b:9c:2f:f9:12:46:55:4a:5a:10:bc - f5: - -prime2: - 00:ee:a5:18:9e:e5:9a:59:d4:d9:0c:42:4c:cf:a1:d7 - ca:8f:b5:45:24:59:f9:83:1f:f4:f2:82:01:50:3c:e0 - ae:bf:17:39:09:cd:5a:71:dd:ab:9e:b6:d6:1e:47:dc - 34:eb:28:c0:4c:77:e5:05:5d:e3:0a:57:bf:65:1f:f8 - 29:68:08:45:ee:da:01:bc:57:c6:35:a7:2c:82:62:64 - 2a:cd:46:eb:54:eb:27:e2:eb:d5:d3:d3:04:ef:05:a1 - 4c:63:ec:d4:23:6f:60:02:71:a8:c3:ab:a5:2f:26:4e - ea:e1:a3:f5:e4:d2:59:19:c9:26:18:c5:4d:45:1e:61 - 1d:53:7c:6a:83:d2:18:40:dd:10:af:e8:24:09:1a:06 - 9b:f1:51:33:8e:13:e0:ce:18:b3:f0:8b:f3:9d:12:b3 - e7:88:01:a1:c1:38:0d:c9:c6:0e:49:99:37:2f:be:60 - e3:37:9d:c6:a8:cb:4b:c5:c0:49:2a:5d:fb:f2:fb:e7 - fb: - -coefficient: - 22:52:c4:c2:c3:dd:30:13:54:c7:95:54:b2:cb:24:b5 - 0c:88:f4:22:a7:6a:13:b3:17:5f:0b:b2:8c:4f:2c:e6 - d3:b6:ca:cd:e6:70:e4:37:5d:65:16:50:ae:b8:cc:bf - 80:81:3d:e5:5c:0e:6d:ec:f9:4e:f5:49:00:fd:63:28 - 2a:ab:03:92:ae:b6:a6:97:ed:f0:97:25:55:06:3f:15 - be:cf:70:22:bd:af:f8:2c:3f:ce:d5:e5:e6:5f:94:d7 - d2:0a:fb:99:d7:ad:ab:d1:3a:b8:fb:6c:b0:47:1c:60 - 81:b1:8c:5e:df:25:8b:93:82:8c:28:52:99:5e:e7:e7 - f8:fc:30:48:bd:43:82:9e:39:2b:7b:14:6b:fb:1b:8a - 7d:f1:1e:06:01:ca:7d:59:54:79:00:fa:76:bd:a9:a1 - 02:ad:c4:7e:0b:56:c2:37:6b:7d:20:5f:53:ef:46:88 - cf:24:e2:4c:25:fc:98:49:40:38:10:19:6f:3c:18:4c - - -exp1: - 00:8b:1e:3a:3e:13:37:f0:c2:0d:96:33:f9:82:53:9c - d7:3d:4e:fa:a3:2b:c0:20:f6:e9:07:92:45:cb:d8:e7 - bd:cf:84:7e:58:30:6d:ac:13:5f:72:5a:a4:65:8c:dd - ec:2d:43:d0:4f:00:03:80:e7:cd:e4:3d:44:ca:88:fd - e0:b0:4b:1a:51:8e:6a:2a:23:98:d4:1c:29:77:69:f2 - 9a:e7:58:8d:2d:64:20:91:19:87:b9:cc:bd:ca:e2:d8 - 1e:00:c1:b0:11:a5:9c:4b:04:bb:da:36:47:5b:2c:18 - 96:59:54:05:cd:eb:09:e6:67:6a:85:c9:50:9f:c1:6f - 11:cf:2e:16:c8:b9:31:1f:f9:29:08:33:43:60:b1:e8 - 07:d0:cf:d1:9b:79:7c:07:06:37:df:15:d4:6b:1d:d4 - ed:f3:7e:53:1d:fa:f5:89:8f:17:30:53:09:6b:d4:92 - c9:df:ba:f7:c9:a4:95:f5:3e:63:d8:50:38:2b:38:b9 - f1: - -exp2: - 00:ed:06:45:41:ec:c2:35:5e:d6:84:fa:84:d7:e4:e3 - 33:69:30:9d:8f:d1:5d:a5:02:e4:82:c8:e5:0d:10:aa - 08:65:fb:66:c7:79:92:cf:6d:5f:bb:af:d5:53:16:04 - 7c:fa:e3:ea:bb:08:8a:0b:9e:88:96:09:39:2b:f3:68 - c3:97:74:40:21:4f:9e:51:b6:cc:43:15:db:7b:54:c6 - 30:4c:da:97:7a:2c:65:dd:58:67:74:90:2e:62:48:b1 - 3f:f2:2f:93:33:ee:b6:e9:36:82:6c:75:db:06:cd:81 - ac:80:98:1c:ee:3c:8e:0a:b2:62:88:4f:ce:c3:4b:bd - 21:27:7e:77:3c:9e:3b:40:91:50:b5:a6:57:c4:42:79 - 36:01:a4:a9:14:00:62:53:d0:ed:47:89:79:59:14:ee - 62:94:0f:2a:dd:82:13:0f:c9:0a:ff:c6:91:ad:75:e5 - 3d:48:4c:08:b8:35:d2:f8:82:57:29:21:57:d0:aa:aa - 69: - - -Public Key PIN: - pin-sha256:SSdvX3918szw/veSWFuQJD3lGynf9mFLiTxpZPj/Jz4= -Public Key ID: - sha256:49276f5f7f75f2ccf0fef792585b90243de51b29dff6614b893c6964f8ff273e - sha1:2e41d8f4c0040eccc2f9010d77c6c6f813c561f4 - ------BEGIN RSA PRIVATE KEY----- -MIIG5QIBAAKCAYEA5khwb98yCyLWiaxvt8u3VoL7F+l9xcy1Vzq4RTgTW7Kx0rJL -bdtMh2YaH6HpZILZXJAbH7eRlGBhAWbiSCBXDr6W1108/3GIHvFoaXBaOuADzXO8 -8cpGk3iBzJhKo8arTnn8jprhMlsmtl2wgOGmenpsoq5HpvedsASTWCyFqMud2EO/ -XRbx9kjlij5VwmtQf4oe2/amGRNlx6C0Emywiag1qEE8Zjntd/9WipySLiMv4oBr -DO8oJqYRuJM78n24gKPjXedFElXnnhfD/l9mGX1xSpm8yU4OKulFAeL6p+F3oFtO -uXiMjdV8N2f5fAoj9Btu4rpP2X0bs9NPnfaExuTrb8gjHHP5AyOA29V7vQuc/VKR -eSCDPOSsvdWMTDKj7goUEKA1ye5nbQu80b5+rdHqzFW6Wqsl8SpZf8mzxQGWCIFe -47UjzyQwHE7AmUC0XFXIIEnGEolpEYFvgyrBAnM6Pvuue+6vvnMkQbRDTzl1Geie -+HigR4s2HWtHIVc3AgMBAAECggGBAIviQPqT+BAvr2ad6pcZFltk4SYbXZ1Dxnwg -XUMe1xOCruYwDAXFiu1Mpl3EusOlgGfr2a4gkjwxd3ukhZwOmROJzpMwPhdlXax+ -NFCoQQc2gNjSj1nI56o5L4+aiuyFiBX5n+L4TgeKuy9YJhmD+N65czg26auRCqab -gO20zdRFK7LtJFdl0sEqctTRHMMm8RUoT6qKX0coM1FaW0g94dcc6Ms2JXxrf8a+ -wlEc3udL1JCgNWb798XSZz1ZoragisYDi9s6OTIoIboRO6N3kFkNcoEBrV9mkrVs -YdKMYOvlYo1ccXKZfBK4HMa67mQXQQS20lnwGg90ytUZHpj0YXXD4VAnr/pIhz3J -EapwPlZkodxC2Ram0hSYSM2t0gkzABBfPGk2gZZ1RPhWldLldoVBhY3xZBQFa/cq -JmrZ8LnQ3tY9fnxsLCZJIl4qBCFPydc8oRt70ScdgDglV3+imUbXYFbmXVBxNrav -plvQktgZDoZLOzTPzerlNeROZeMgkQKBwQD3B6q+JPEvJuoORYawRQXfuFnWO0Bc -3ci/GD52dOgxNcqw1GPfTUzVDEZuMXE8F15FTlyhlk9pXpK96AknhVDjKR27jvks -Qa8PyODTcGrUuWdDCORKyRL20n59vGlSukiWCn5C526C6AxsG6UB8DYsrqQv2GLv -qxxHOph5QGjd3mq0jFMDCXgRJzZv5Cv79E2/EzA3H1H9L4TZuWLqkajocp14FL1a -nh8GEnAZvTuAslwzi9ZbnC/5EkZVSloQvPUCgcEA7qUYnuWaWdTZDEJMz6HXyo+1 -RSRZ+YMf9PKCAVA84K6/FzkJzVpx3auettYeR9w06yjATHflBV3jCle/ZR/4KWgI -Re7aAbxXxjWnLIJiZCrNRutU6yfi69XT0wTvBaFMY+zUI29gAnGow6ulLyZO6uGj -9eTSWRnJJhjFTUUeYR1TfGqD0hhA3RCv6CQJGgab8VEzjhPgzhiz8IvznRKz54gB -ocE4DcnGDkmZNy++YOM3ncaoy0vFwEkqXfvy++f7AoHBAIseOj4TN/DCDZYz+YJT -nNc9TvqjK8Ag9ukHkkXL2Oe9z4R+WDBtrBNfclqkZYzd7C1D0E8AA4DnzeQ9RMqI -/eCwSxpRjmoqI5jUHCl3afKa51iNLWQgkRmHucy9yuLYHgDBsBGlnEsEu9o2R1ss -GJZZVAXN6wnmZ2qFyVCfwW8Rzy4WyLkxH/kpCDNDYLHoB9DP0Zt5fAcGN98V1Gsd -1O3zflMd+vWJjxcwUwlr1JLJ37r3yaSV9T5j2FA4Kzi58QKBwQDtBkVB7MI1XtaE -+oTX5OMzaTCdj9FdpQLkgsjlDRCqCGX7Zsd5ks9tX7uv1VMWBHz64+q7CIoLnoiW -CTkr82jDl3RAIU+eUbbMQxXbe1TGMEzal3osZd1YZ3SQLmJIsT/yL5Mz7rbpNoJs -ddsGzYGsgJgc7jyOCrJiiE/Ow0u9ISd+dzyeO0CRULWmV8RCeTYBpKkUAGJT0O1H -iXlZFO5ilA8q3YITD8kK/8aRrXXlPUhMCLg10viCVykhV9CqqmkCgcAiUsTCw90w -E1THlVSyyyS1DIj0IqdqE7MXXwuyjE8s5tO2ys3mcOQ3XWUWUK64zL+AgT3lXA5t -7PlO9UkA/WMoKqsDkq62ppft8JclVQY/Fb7PcCK9r/gsP87V5eZflNfSCvuZ162r -0Tq4+2ywRxxggbGMXt8li5OCjChSmV7n5/j8MEi9Q4KeOSt7FGv7G4p98R4GAcp9 -WVR5APp2vamhAq3EfgtWwjdrfSBfU+9GiM8k4kwl/JhJQDgQGW88GEw= ------END RSA PRIVATE KEY----- diff --git a/test/test_certs/ca.cert.pem b/test/test_certs/ca.cert.pem deleted file mode 100644 index 9832d1231..000000000 --- a/test/test_certs/ca.cert.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFoDCCA4igAwIBAgIMWId0/Tdzb0Anm7xOMA0GCSqGSIb3DQEBCwUAMHQxFTAT -BgNVBAMTDGxvY2FsaG9zdCBDQTEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBGYWN0 -b3J5MRIwEAYDVQQHDAlHw7Z0ZWJvcmcxGjAYBgNVBAgMEVbDpHN0cmEgR8O2dGFs -YW5kMQswCQYDVQQGEwJTRTAeFw0xNzAxMjQxNTM4MzdaFw0xODAxMjQxNTM4Mzda -MHQxFTATBgNVBAMTDGxvY2FsaG9zdCBDQTEeMBwGA1UECgwVR8O2dGVib3JnIEJp -dCBGYWN0b3J5MRIwEAYDVQQHDAlHw7Z0ZWJvcmcxGjAYBgNVBAgMEVbDpHN0cmEg -R8O2dGFsYW5kMQswCQYDVQQGEwJTRTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC -AgoCggIBAKc95KldnRqe+UVbHdcQLOfCpCevV8vJjwlgiEZX6iPaekPyVjGnxAQ3 -Zq2j1mepTbRh37FBb1xl8vPu2UHRe7d79LF7M6SlP/Wz/wpi74hiDaOgnSVS/7c+ -wg/nJqtuCUPAJUW+z5m3PtlyGj/4Xt6yjBQ4wN1uLY45JfIE6OdW9KRTAy5s+Kpf -qGSyb9HLAjsNgB65BCL8/ZQYc9v5P/2woyqR0Ee09R1JL0gvJEqBvdvl7DcdRhqt -XFl1f7wnEW+Qwg0d+YkFnGX8gL3f9CYWaKm58Ys0A6kq/EzlbPCTn8PqgEP8pVZ1 -/+WGVirULfqjzLOwhUEHzkCggKSpLIkrm5OzDtx1lQ9KSF9M9q+hxiNzg9djMhvI -fjOOkL8M7e2+gmnfuJ8oJSkf6md9lNEar/r+K1B9p6vOF3i6elCu79L4xSnMtM2O -88G9TTjwRX9CtT2qqCLbMPm8eKc/yVmMNhLDWyUyfXZ9BLrXhFAwclwCRwMHfnkC -ORW1EZvFGtmimxJSTDlCWYb1/ciB2MSIJktNpNPUdV/IhdEVDZjBGtBxEfWaW3CE -PU1QbakBMNdfLfGHGQhFOklrZuQFngwEEHaSgojxXkz/Yloh+rLHZnSltfchBaVm -DwzdqdD4dztMNI03VB5isZtl10cPMjfiEQ9u9ch98wZj3yLidtJfAgMBAAGjMjAw -MA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIs7BvGArBW1hgbUU7xdTWfwJAvg -MA0GCSqGSIb3DQEBCwUAA4ICAQB9D0662JXbMwqQL5pvPEUZiBN2DSmuijNEIFEY -Q83zy0PcNg9Gce3tBdy4GI5gk5HpfDWwd78ahnqotxZUVt2RbdsaXvZ5VYRpIiyZ -dY9fBG1g23iE6cmlW8FTRkcke/cjOOtHfohys9tFdRrS+pIMzP+BB9tOoxfpQ+8w -LgHEzZ4I0EGKsqoSPOPHDug9ZGN3IPZTRKdCxahV4cc0nxVbYFebJqpocv7xkXLb -HQA5HkhImKDPzY6cX+n+zL9f8F0KozXqt/f9AP3v2F/QNWRSjiiPCFw5qhKiyeRv -bbrW01XJsUDmPxL08U0fenPYC9RuzDGWeJJxFpG6OSfn/dOry3As7bvrh6C9Q5ox -QLrEFRIbrq37DICBTTs4LW0wG1A0kKLxTVsTK7e99FCZwfKO2K6ra3UPNwlQYxlv -C7NMEtbCGx43gWoQngpzYNvoXyb8j5GhzEeniELdCzISXMBx3tPhGc9ZWvYtMe5c -ZdN67pcx6hNPs3PpVuHS5otDUe2rw7DlqeMJokkoPgFD7CDioRZbq/kMvbkHTXAs -0rqUkH9SP14q6SbuA7t/j43JGrzKiayKYdXmn/EGd31GUdI6hOWCLzsLKg/8lDA1 -TposJN5apxRy3FVuuFcFbG7TjEFu5LGQUJrKQYbrnED74btoALBenrNHEznQhXfO -HbNnhg== ------END CERTIFICATE----- diff --git a/test/test_certs/ca.key.pem b/test/test_certs/ca.key.pem deleted file mode 100644 index fcb891c37..000000000 --- a/test/test_certs/ca.key.pem +++ /dev/null @@ -1,240 +0,0 @@ -Public Key Info: - Public Key Algorithm: RSA - Key Security Level: High (4096 bits) - -modulus: - 00:a7:3d:e4:a9:5d:9d:1a:9e:f9:45:5b:1d:d7:10:2c - e7:c2:a4:27:af:57:cb:c9:8f:09:60:88:46:57:ea:23 - da:7a:43:f2:56:31:a7:c4:04:37:66:ad:a3:d6:67:a9 - 4d:b4:61:df:b1:41:6f:5c:65:f2:f3:ee:d9:41:d1:7b - b7:7b:f4:b1:7b:33:a4:a5:3f:f5:b3:ff:0a:62:ef:88 - 62:0d:a3:a0:9d:25:52:ff:b7:3e:c2:0f:e7:26:ab:6e - 09:43:c0:25:45:be:cf:99:b7:3e:d9:72:1a:3f:f8:5e - de:b2:8c:14:38:c0:dd:6e:2d:8e:39:25:f2:04:e8:e7 - 56:f4:a4:53:03:2e:6c:f8:aa:5f:a8:64:b2:6f:d1:cb - 02:3b:0d:80:1e:b9:04:22:fc:fd:94:18:73:db:f9:3f - fd:b0:a3:2a:91:d0:47:b4:f5:1d:49:2f:48:2f:24:4a - 81:bd:db:e5:ec:37:1d:46:1a:ad:5c:59:75:7f:bc:27 - 11:6f:90:c2:0d:1d:f9:89:05:9c:65:fc:80:bd:df:f4 - 26:16:68:a9:b9:f1:8b:34:03:a9:2a:fc:4c:e5:6c:f0 - 93:9f:c3:ea:80:43:fc:a5:56:75:ff:e5:86:56:2a:d4 - 2d:fa:a3:cc:b3:b0:85:41:07:ce:40:a0:80:a4:a9:2c - 89:2b:9b:93:b3:0e:dc:75:95:0f:4a:48:5f:4c:f6:af - a1:c6:23:73:83:d7:63:32:1b:c8:7e:33:8e:90:bf:0c - ed:ed:be:82:69:df:b8:9f:28:25:29:1f:ea:67:7d:94 - d1:1a:af:fa:fe:2b:50:7d:a7:ab:ce:17:78:ba:7a:50 - ae:ef:d2:f8:c5:29:cc:b4:cd:8e:f3:c1:bd:4d:38:f0 - 45:7f:42:b5:3d:aa:a8:22:db:30:f9:bc:78:a7:3f:c9 - 59:8c:36:12:c3:5b:25:32:7d:76:7d:04:ba:d7:84:50 - 30:72:5c:02:47:03:07:7e:79:02:39:15:b5:11:9b:c5 - 1a:d9:a2:9b:12:52:4c:39:42:59:86:f5:fd:c8:81:d8 - c4:88:26:4b:4d:a4:d3:d4:75:5f:c8:85:d1:15:0d:98 - c1:1a:d0:71:11:f5:9a:5b:70:84:3d:4d:50:6d:a9:01 - 30:d7:5f:2d:f1:87:19:08:45:3a:49:6b:66:e4:05:9e - 0c:04:10:76:92:82:88:f1:5e:4c:ff:62:5a:21:fa:b2 - c7:66:74:a5:b5:f7:21:05:a5:66:0f:0c:dd:a9:d0:f8 - 77:3b:4c:34:8d:37:54:1e:62:b1:9b:65:d7:47:0f:32 - 37:e2:11:0f:6e:f5:c8:7d:f3:06:63:df:22:e2:76:d2 - 5f: - -public exponent: - 01:00:01: - -private exponent: - 1c:34:af:14:f5:69:e2:ac:7f:23:f8:5b:0f:03:76:5f - 5c:0f:6d:76:00:1b:a9:91:cb:26:11:b5:b3:6c:14:c1 - eb:2e:fc:77:17:06:d1:63:58:a8:a3:8e:67:41:b2:67 - 1d:8e:08:39:0f:ed:25:2b:38:8f:75:70:04:ce:bc:cb - d4:47:0f:8b:d4:c8:e6:e9:e3:99:88:e1:0a:90:95:72 - dc:14:05:a9:9b:3a:e5:4f:d5:70:cb:57:d3:c8:c3:d5 - 22:2d:0c:dc:37:73:31:dc:9a:e7:f7:7b:7f:e0:76:b7 - 9e:6b:4b:99:ca:c5:4d:a8:b7:3c:e2:2e:70:2f:8b:9c - b9:c3:e1:10:3f:4b:37:a1:1c:62:fd:20:af:05:35:f4 - d9:5a:cc:89:e1:f0:55:aa:4f:66:23:bd:9e:92:e9:fc - f2:46:82:8e:77:0f:30:f0:ca:10:a6:bd:c8:dd:99:07 - 4a:ab:04:9b:13:2c:87:bf:46:0f:b6:32:bf:e9:4e:b9 - 7d:14:6f:f5:e0:61:4e:7f:b8:c1:e6:95:1b:f0:e2:6f - 6e:06:16:e1:fa:5f:dc:d4:e5:3a:0f:b5:34:58:6c:36 - a9:92:54:41:6f:b5:f3:78:a2:8c:16:fc:d2:19:85:11 - 80:1d:12:10:18:2c:51:2c:6c:d7:af:7a:22:cb:39:6c - 5f:18:2b:75:9d:1c:45:70:31:22:da:a5:c8:72:bb:e8 - 85:00:56:3d:18:f0:0e:3a:a7:73:49:a4:b1:d2:42:9e - 9e:4d:21:9d:69:d5:b7:e0:1e:b8:91:e4:94:1e:b5:f1 - 31:3e:1e:f3:20:2c:ab:e7:bf:3f:f1:88:15:4c:bf:a6 - e8:03:78:35:8a:94:d3:2d:e9:c7:07:3a:f8:d8:c8:9d - 7d:87:3a:c2:a7:f7:4d:62:d3:2c:09:e1:7b:a6:e5:99 - ba:7c:24:08:16:3a:2a:29:21:e5:78:47:fb:13:f8:e4 - de:05:2b:ac:73:f0:08:9d:d5:4d:fd:ca:6a:50:1e:b7 - 09:f0:31:47:5c:b7:09:9b:3a:af:0d:df:fb:89:88:4b - 98:1e:2e:dc:f7:4a:2b:74:a6:4b:1d:ef:76:5f:fb:36 - 6f:ce:f4:34:8b:40:38:58:8d:3b:7e:42:97:74:0a:0c - d8:41:98:81:7b:18:bb:e3:b9:2e:f9:3a:ef:91:ca:41 - 46:e0:a0:99:70:e0:32:ce:09:5a:78:ed:2e:3a:4e:de - b3:a4:85:1a:05:8a:fe:bf:25:a1:cf:d7:de:6f:33:81 - 33:6e:c3:82:5d:de:f9:93:ba:06:46:ec:d1:d3:3a:80 - 39:94:d9:35:5f:58:df:55:2d:b6:4d:0b:66:e2:f8:39 - - -prime1: - 00:db:a2:a3:71:db:9f:13:8a:1e:4a:a6:b9:8a:4e:bd - eb:89:c3:27:1d:29:42:c4:71:0e:cc:5e:59:fd:63:72 - d6:5d:d4:1b:16:57:e6:9e:4e:e7:b6:dc:2b:30:a2:55 - be:36:94:0c:e1:06:ea:e0:c3:9f:fa:89:d0:eb:05:f0 - a2:ed:9c:45:42:46:4b:85:45:f2:ab:63:b9:4f:6e:1b - c4:fd:15:a4:09:26:f0:b1:77:e3:ee:2a:30:3b:4d:e2 - 0e:53:35:94:9e:26:aa:b6:65:d9:1d:de:94:79:5f:47 - 80:7f:34:cf:d9:2a:63:0f:1a:9f:4a:ea:10:95:7c:2f - 13:4b:a4:37:5a:aa:cd:55:3c:6f:38:a2:10:06:2a:4e - 26:2b:47:22:ae:c6:73:38:8a:cc:0c:05:92:86:ae:9a - 37:aa:c5:65:92:72:dc:d0:45:3f:a3:b6:b2:c0:1f:b6 - 1a:66:16:a8:45:3a:8e:14:d3:a0:1e:85:a5:59:64:6e - a5:49:43:76:62:52:79:4b:85:88:af:ec:07:5e:32:39 - 6e:71:65:2f:90:cc:32:7d:1d:db:8c:40:b7:ab:e9:46 - 22:42:29:0f:32:af:7d:05:6a:67:ab:9b:80:57:5e:12 - 4f:26:54:a6:3b:9c:88:8c:58:5f:20:02:f3:4c:a6:32 - 5d: - -prime2: - 00:c2:ee:86:bc:d2:32:81:f8:a1:7c:3e:4b:6e:03:66 - 34:dc:1f:2f:db:44:ec:de:2a:d0:58:fc:0c:08:6a:4c - 8d:27:2f:d1:c9:10:18:ed:ee:00:72:75:78:6e:fe:08 - 70:5d:77:57:b9:0f:5f:0e:70:ad:50:1e:75:52:b0:80 - aa:70:44:44:bb:38:ae:cb:a4:4e:e1:29:3b:3a:98:8f - 4a:48:f3:8d:2a:89:d5:02:f3:e2:0b:3e:f5:d1:14:af - 87:cf:69:d1:4f:ca:d5:af:2d:c7:53:61:09:c7:52:09 - 62:c7:66:9e:87:e3:6c:94:a9:44:a9:ae:48:f9:0a:c9 - 65:b0:85:87:9a:b3:f8:62:f6:8d:7e:24:84:5d:cf:8b - 0e:44:8e:c4:4e:67:41:ab:f2:b0:76:27:c1:62:dd:3b - 65:07:23:27:c4:2c:43:1f:8b:65:7c:db:6c:fa:05:dd - 89:c7:21:a3:df:96:42:7a:e7:12:e3:f2:02:a6:f0:5a - 9e:50:37:29:0a:fa:de:4c:82:41:9f:9d:cc:43:b6:c0 - e4:5a:2d:40:ad:3f:61:f6:31:b2:76:ff:57:86:ec:b9 - 57:26:db:b1:f6:3b:ee:eb:82:f0:ca:15:d1:c9:ea:3b - 81:b5:f1:a9:ca:b7:88:77:fb:dc:0f:b4:f5:32:95:83 - eb: - -coefficient: - 63:04:df:d0:5f:bd:e4:1f:94:21:97:c6:60:6e:1c:e8 - fb:1a:d8:96:82:a4:4d:2d:2b:97:0c:dc:58:20:63:a7 - 9d:dc:bf:4c:0a:49:59:12:2f:0b:b2:2d:e6:f5:3a:1e - 88:d9:31:db:49:38:22:0b:ac:36:68:35:d5:49:10:6e - 2f:45:7e:55:32:4b:7f:cc:b2:23:2f:c3:34:6e:61:ba - 9f:20:66:52:e1:89:80:d2:3f:9e:84:74:bb:46:dc:55 - 93:6a:11:78:8c:4c:d7:84:8c:7f:ee:8c:cd:7a:13:66 - d4:6c:15:43:6f:8d:c3:ed:86:ba:41:7d:d0:6e:24:b1 - a7:58:7a:b4:f5:47:7d:b4:8c:38:6f:18:39:4a:e4:04 - 04:f9:5f:e1:1a:b1:f8:10:e1:05:e2:7f:d6:64:94:1f - f0:7f:52:bf:01:79:49:06:5f:92:21:24:0e:d8:e3:53 - 65:09:12:ff:33:b5:99:83:49:30:2a:8b:44:03:18:6b - 30:84:ff:61:91:63:2e:6a:72:16:e6:40:d8:10:f9:60 - cc:09:1c:4a:15:3c:49:9a:86:b3:66:f6:83:55:8d:86 - 33:c3:13:e1:61:d2:5e:7d:d8:02:67:c5:5d:3e:82:d1 - 8c:f5:93:4a:83:35:1c:7b:d7:a0:e5:f6:76:20:dc:e3 - - -exp1: - 00:8c:4e:67:6f:32:c7:7a:69:ff:53:dd:12:26:02:8b - 5c:ce:52:b6:c7:f0:35:d0:dc:10:82:09:bc:1f:1c:ca - 9d:9a:af:06:e6:cd:1c:6c:01:28:11:2e:b1:00:8e:93 - 8b:c1:bd:ee:44:4a:70:fd:48:bb:08:07:c6:48:7d:30 - ee:44:30:52:26:36:55:c4:3b:9c:fc:53:e6:6b:91:81 - 77:e3:dd:51:3e:ef:7b:4f:43:b1:7d:d1:c1:a2:00:ec - ea:0d:c9:ea:b8:e5:26:a5:02:87:22:87:af:fd:a1:1a - 42:42:00:f0:ce:60:3f:8b:c4:8c:02:05:68:b6:b5:f0 - 34:b9:5b:9c:68:f5:37:da:bd:92:a7:71:10:1f:80:23 - 10:4b:11:54:5c:a6:35:b0:e7:29:d6:7c:30:d0:8c:f5 - c3:9d:fa:20:1a:df:f9:a3:aa:b9:2e:d2:65:7a:4a:47 - c0:b1:7a:c3:e0:05:ba:02:1f:f6:e7:c8:a4:8a:68:98 - 2e:4a:91:28:2d:43:56:6d:22:4e:e1:63:3c:89:b6:37 - ee:0f:ce:7e:06:58:0b:e0:d5:94:0d:12:c2:c2:b5:70 - b2:2d:e5:a0:e9:d6:25:5d:b2:4d:ab:d8:d8:b1:f7:4c - 66:f7:ab:97:cd:f8:ff:59:f8:92:3c:16:e6:6a:0e:b9 - 99: - -exp2: - 6c:20:a5:8a:9d:ca:38:80:33:0b:58:fe:bc:52:94:62 - 23:ec:04:be:cb:57:dc:d0:c0:eb:6b:3c:3d:7a:c1:65 - d6:e3:d6:a9:82:0d:62:05:90:27:ed:73:55:96:02:16 - 5a:32:35:ed:c8:52:01:0f:8d:83:90:b1:a1:a1:98:6d - 72:82:db:e1:42:4a:eb:59:04:0c:2f:b3:d4:08:06:0b - bd:b5:a3:8b:82:13:2f:65:1a:f1:04:06:94:e8:b5:89 - 4f:0b:32:78:e1:59:70:a7:a3:16:64:10:69:88:de:fd - f4:ff:be:56:40:57:dd:8a:9b:34:c8:21:60:4b:94:75 - 9c:da:24:e3:32:8b:48:51:dd:20:a8:9e:e7:ee:95:02 - e0:12:95:5f:ac:35:28:6d:21:67:19:1c:53:7b:c6:53 - 7f:07:08:2a:f0:26:77:9b:fa:3a:ac:f8:a1:23:99:c4 - b0:39:bf:cd:e9:6a:8d:04:1a:5d:68:c4:01:d7:f7:5c - 33:c0:b5:3a:8a:f7:be:56:ce:91:fc:94:ee:c6:b3:ee - b6:fa:bd:12:9c:c2:f9:2e:8e:01:26:18:66:16:6f:a4 - a1:40:62:51:e4:e2:dd:ae:af:64:59:80:1a:51:9c:9a - 91:d3:30:4c:eb:4b:7e:ca:ad:41:e5:2d:d1:d6:4f:eb - - - -Public Key ID: - sha256:1C:1B:09:C3:1E:D5:B3:D7:16:60:45:8E:DB:B4:64:4D:67:F3:81:11:49:3B:D3:A1:F7:EE:4E:75:27:F7:66:03 - sha1:8B:3B:06:F1:80:AC:15:B5:86:06:D4:53:BC:5D:4D:67:F0:24:0B:E0 -Public key's random art: -+--[ RSA 4096]----+ -|o...+. ...o+.+ | -| ..+ o. ...B | -| .oo+ oE. . . | -| .+.o. . | -| o + S | -|. . .. . | -| .. . | -| o. | -| ... | -+-----------------+ - ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEApz3kqV2dGp75RVsd1xAs58KkJ69Xy8mPCWCIRlfqI9p6Q/JW -MafEBDdmraPWZ6lNtGHfsUFvXGXy8+7ZQdF7t3v0sXszpKU/9bP/CmLviGINo6Cd -JVL/tz7CD+cmq24JQ8AlRb7Pmbc+2XIaP/he3rKMFDjA3W4tjjkl8gTo51b0pFMD -Lmz4ql+oZLJv0csCOw2AHrkEIvz9lBhz2/k//bCjKpHQR7T1HUkvSC8kSoG92+Xs -Nx1GGq1cWXV/vCcRb5DCDR35iQWcZfyAvd/0JhZoqbnxizQDqSr8TOVs8JOfw+qA -Q/ylVnX/5YZWKtQt+qPMs7CFQQfOQKCApKksiSubk7MO3HWVD0pIX0z2r6HGI3OD -12MyG8h+M46Qvwzt7b6Cad+4nyglKR/qZ32U0Rqv+v4rUH2nq84XeLp6UK7v0vjF -Kcy0zY7zwb1NOPBFf0K1PaqoItsw+bx4pz/JWYw2EsNbJTJ9dn0EuteEUDByXAJH -Awd+eQI5FbURm8Ua2aKbElJMOUJZhvX9yIHYxIgmS02k09R1X8iF0RUNmMEa0HER -9ZpbcIQ9TVBtqQEw118t8YcZCEU6SWtm5AWeDAQQdpKCiPFeTP9iWiH6ssdmdKW1 -9yEFpWYPDN2p0Ph3O0w0jTdUHmKxm2XXRw8yN+IRD271yH3zBmPfIuJ20l8CAwEA -AQKCAgAcNK8U9WnirH8j+FsPA3ZfXA9tdgAbqZHLJhG1s2wUwesu/HcXBtFjWKij -jmdBsmcdjgg5D+0lKziPdXAEzrzL1EcPi9TI5unjmYjhCpCVctwUBambOuVP1XDL -V9PIw9UiLQzcN3Mx3Jrn93t/4Ha3nmtLmcrFTai3POIucC+LnLnD4RA/SzehHGL9 -IK8FNfTZWsyJ4fBVqk9mI72ekun88kaCjncPMPDKEKa9yN2ZB0qrBJsTLIe/Rg+2 -Mr/pTrl9FG/14GFOf7jB5pUb8OJvbgYW4fpf3NTlOg+1NFhsNqmSVEFvtfN4oowW -/NIZhRGAHRIQGCxRLGzXr3oiyzlsXxgrdZ0cRXAxItqlyHK76IUAVj0Y8A46p3NJ -pLHSQp6eTSGdadW34B64keSUHrXxMT4e8yAsq+e/P/GIFUy/pugDeDWKlNMt6ccH -OvjYyJ19hzrCp/dNYtMsCeF7puWZunwkCBY6Kikh5XhH+xP45N4FK6xz8Aid1U39 -ympQHrcJ8DFHXLcJmzqvDd/7iYhLmB4u3PdKK3SmSx3vdl/7Nm/O9DSLQDhYjTt+ -Qpd0CgzYQZiBexi747ku+TrvkcpBRuCgmXDgMs4JWnjtLjpO3rOkhRoFiv6/JaHP -195vM4EzbsOCXd75k7oGRuzR0zqAOZTZNV9Y31Uttk0LZuL4OQKCAQEA26Kjcduf -E4oeSqa5ik6964nDJx0pQsRxDsxeWf1jctZd1BsWV+aeTue23CswolW+NpQM4Qbq -4MOf+onQ6wXwou2cRUJGS4VF8qtjuU9uG8T9FaQJJvCxd+PuKjA7TeIOUzWUniaq -tmXZHd6UeV9HgH80z9kqYw8an0rqEJV8LxNLpDdaqs1VPG84ohAGKk4mK0cirsZz -OIrMDAWShq6aN6rFZZJy3NBFP6O2ssAfthpmFqhFOo4U06AehaVZZG6lSUN2YlJ5 -S4WIr+wHXjI5bnFlL5DMMn0d24xAt6vpRiJCKQ8yr30Famerm4BXXhJPJlSmO5yI -jFhfIALzTKYyXQKCAQEAwu6GvNIygfihfD5LbgNmNNwfL9tE7N4q0Fj8DAhqTI0n -L9HJEBjt7gBydXhu/ghwXXdXuQ9fDnCtUB51UrCAqnBERLs4rsukTuEpOzqYj0pI -840qidUC8+ILPvXRFK+Hz2nRT8rVry3HU2EJx1IJYsdmnofjbJSpRKmuSPkKyWWw -hYeas/hi9o1+JIRdz4sORI7ETmdBq/KwdifBYt07ZQcjJ8QsQx+LZXzbbPoF3YnH -IaPflkJ65xLj8gKm8FqeUDcpCvreTIJBn53MQ7bA5FotQK0/YfYxsnb/V4bsuVcm -27H2O+7rgvDKFdHJ6juBtfGpyreId/vcD7T1MpWD6wKCAQEAjE5nbzLHemn/U90S -JgKLXM5StsfwNdDcEIIJvB8cyp2arwbmzRxsASgRLrEAjpOLwb3uREpw/Ui7CAfG -SH0w7kQwUiY2VcQ7nPxT5muRgXfj3VE+73tPQ7F90cGiAOzqDcnquOUmpQKHIoev -/aEaQkIA8M5gP4vEjAIFaLa18DS5W5xo9TfavZKncRAfgCMQSxFUXKY1sOcp1nww -0Iz1w536IBrf+aOquS7SZXpKR8CxesPgBboCH/bnyKSKaJguSpEoLUNWbSJO4WM8 -ibY37g/OfgZYC+DVlA0SwsK1cLIt5aDp1iVdsk2r2Nix90xm96uXzfj/WfiSPBbm -ag65mQKCAQBsIKWKnco4gDMLWP68UpRiI+wEvstX3NDA62s8PXrBZdbj1qmCDWIF -kCftc1WWAhZaMjXtyFIBD42DkLGhoZhtcoLb4UJK61kEDC+z1AgGC721o4uCEy9l -GvEEBpTotYlPCzJ44Vlwp6MWZBBpiN799P++VkBX3YqbNMghYEuUdZzaJOMyi0hR -3SConufulQLgEpVfrDUobSFnGRxTe8ZTfwcIKvAmd5v6Oqz4oSOZxLA5v83pao0E -Gl1oxAHX91wzwLU6ive+Vs6R/JTuxrPutvq9EpzC+S6OASYYZhZvpKFAYlHk4t2u -r2RZgBpRnJqR0zBM60t+yq1B5S3R1k/rAoIBAGME39BfveQflCGXxmBuHOj7GtiW -gqRNLSuXDNxYIGOnndy/TApJWRIvC7It5vU6HojZMdtJOCILrDZoNdVJEG4vRX5V -Mkt/zLIjL8M0bmG6nyBmUuGJgNI/noR0u0bcVZNqEXiMTNeEjH/ujM16E2bUbBVD -b43D7Ya6QX3QbiSxp1h6tPVHfbSMOG8YOUrkBAT5X+EasfgQ4QXif9ZklB/wf1K/ -AXlJBl+SISQO2ONTZQkS/zO1mYNJMCqLRAMYazCE/2GRYy5qchbmQNgQ+WDMCRxK -FTxJmoazZvaDVY2GM8MT4WHSXn3YAmfFXT6C0Yz1k0qDNRx716Dl9nYg3OM= ------END RSA PRIVATE KEY----- diff --git a/test/test_certs/client.cert.pem b/test/test_certs/client.cert.pem deleted file mode 100644 index 27838d74f..000000000 --- a/test/test_certs/client.cert.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIMWId1Agdxxug/oIa6MA0GCSqGSIb3DQEBCwUAMHQxFTAT -BgNVBAMTDGxvY2FsaG9zdCBDQTEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBGYWN0 -b3J5MRIwEAYDVQQHDAlHw7Z0ZWJvcmcxGjAYBgNVBAgMEVbDpHN0cmEgR8O2dGFs -YW5kMQswCQYDVQQGEwJTRTAeFw0xNzAxMjQxNTM4NDJaFw0xODAxMjQxNTM4NDJa -MDQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBG -YWN0b3J5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApOz4wcdTiEdj -O192j39Q1EhzLq9qqZghpWUxQHSV0/Fpm7kUj/wtK1xky130+H2ab+k/Sp8jjda0 -TVV7es7yXYQM2iYEDIc1i3oCtygFkISFpzNG5SGLV2GANovwYfgPhvpnGo9inAVJ -lXv2DJouLBxsL3c86dA3auMjcJ8FNgfvSJ7So7vEdoZQo1QGKpToUUVPD9e+K/y+ -j4WI8swP8lJaZ/EIDK74ESAyyk7R+jYjItF4GrwR7RM7CxWHOENwO/3pxbiJF+oW -XOt+2jsTTKyL1FZJHI2beBcPvQql9baBY/q0koT7cS8PJiGI/D6TqXQ25XkbTG/l -dmzJ7K5toLJ+7iUcdt+q8sF0owS4hR/3VUXM24DgBduzvmihD9G0k0wDNmjHu3te -8tGQogvMJRTUtiw1AEO2DiUUZqd6p/pFGrJfRyxU6nHBvr1ADeClBwcQB0hH2mVD -59affexzBsUwhouk3v0fTgNQpX2/qfeKwVbnV4bEN1fd+xSwzdmSelLa6rE+pYER -JOl+zdLSq0AtZE2GTp2A60jk+AurALcQqhdNU9YV8tCVhFTidO49GXqTmuAJAzvu -SAoIqCFuU1TWeiNQCHI3clzxUAZcqDBxTZUvbkP3QK7v0Ib5c7HiRp5kmO4CQuUg -y8mT1RZhaMp9qIy3HKUNEBdYwhj45wECAwEAAaN2MHQwDAYDVR0TAQH/BAIwADAT -BgNVHSUEDDAKBggrBgEFBQcDAjAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBQ3 -DVC7k5DA5XjW4dD+mWRthF48NjAfBgNVHSMEGDAWgBSLOwbxgKwVtYYG1FO8XU1n -8CQL4DANBgkqhkiG9w0BAQsFAAOCAgEASw6rojEtxVNLmqhuCGRXpBkeJcEC3hgd -usOmw3QFwMRwG0JkQuDTZobs20bWCSJzCnGcou6mvdOhjtaErZ/KUA2uAr9YvlmO -+F7mpXlGjfETm3Ta8JzvTtqgCPjQmz+Wn7nnFmwl9MLOOh//NXQ1KJd/ixWtpaLG -GhY3zduNczLT7O+qganFneostaZFpa/BL3PVXoI669zp7GmzJYSjuxQIyAMP/hKJ -aFfTNIv9WhPzXrzEviLspJ7OwfWW/S46WZWcXzauXiYS5y21rfFSBSAo1J3oRV95 -qi2IMM4PVpwQISMPwdJe3f2+Gc6qn3TaHFrnSv8OuRNOdvuqV9MLn9uxovgTTyw4 -j1sb7kO6OoLCqrbdZ+lTHy48jahpV3q+Vcep8WA+fC42sV/lfMuuS3EaQTJIfu89 -+R+pFQPco60risM8Gy/k0QkwxVwoxsq5xM+SaGyHpEazSasX/qJBj+VH97a9rlje -fzPpmApWRtZNMNx4qSx7ZEFVtSynM7vDWsWsB9x+OYdObzl4bbZUZ3MSCY0/smGO -PIbNPmqhlr8bRuJGGlXuftOGLPnssWrZSbQhMzBJQWGiKsHQF+AsdQzaDrtgwuWM -6REkiXd/cs0QXZh9P2ZzzIqKuIwwOazppJ5mdenWx/0X3Ex7PfdiGZPL8f8DEE7/ -lHXLHvTvWag= ------END CERTIFICATE----- diff --git a/test/test_certs/client.key.pem b/test/test_certs/client.key.pem deleted file mode 100644 index 6bcd83661..000000000 --- a/test/test_certs/client.key.pem +++ /dev/null @@ -1,240 +0,0 @@ -Public Key Info: - Public Key Algorithm: RSA - Key Security Level: High (4096 bits) - -modulus: - 00:a4:ec:f8:c1:c7:53:88:47:63:3b:5f:76:8f:7f:50 - d4:48:73:2e:af:6a:a9:98:21:a5:65:31:40:74:95:d3 - f1:69:9b:b9:14:8f:fc:2d:2b:5c:64:cb:5d:f4:f8:7d - 9a:6f:e9:3f:4a:9f:23:8d:d6:b4:4d:55:7b:7a:ce:f2 - 5d:84:0c:da:26:04:0c:87:35:8b:7a:02:b7:28:05:90 - 84:85:a7:33:46:e5:21:8b:57:61:80:36:8b:f0:61:f8 - 0f:86:fa:67:1a:8f:62:9c:05:49:95:7b:f6:0c:9a:2e - 2c:1c:6c:2f:77:3c:e9:d0:37:6a:e3:23:70:9f:05:36 - 07:ef:48:9e:d2:a3:bb:c4:76:86:50:a3:54:06:2a:94 - e8:51:45:4f:0f:d7:be:2b:fc:be:8f:85:88:f2:cc:0f - f2:52:5a:67:f1:08:0c:ae:f8:11:20:32:ca:4e:d1:fa - 36:23:22:d1:78:1a:bc:11:ed:13:3b:0b:15:87:38:43 - 70:3b:fd:e9:c5:b8:89:17:ea:16:5c:eb:7e:da:3b:13 - 4c:ac:8b:d4:56:49:1c:8d:9b:78:17:0f:bd:0a:a5:f5 - b6:81:63:fa:b4:92:84:fb:71:2f:0f:26:21:88:fc:3e - 93:a9:74:36:e5:79:1b:4c:6f:e5:76:6c:c9:ec:ae:6d - a0:b2:7e:ee:25:1c:76:df:aa:f2:c1:74:a3:04:b8:85 - 1f:f7:55:45:cc:db:80:e0:05:db:b3:be:68:a1:0f:d1 - b4:93:4c:03:36:68:c7:bb:7b:5e:f2:d1:90:a2:0b:cc - 25:14:d4:b6:2c:35:00:43:b6:0e:25:14:66:a7:7a:a7 - fa:45:1a:b2:5f:47:2c:54:ea:71:c1:be:bd:40:0d:e0 - a5:07:07:10:07:48:47:da:65:43:e7:d6:9f:7d:ec:73 - 06:c5:30:86:8b:a4:de:fd:1f:4e:03:50:a5:7d:bf:a9 - f7:8a:c1:56:e7:57:86:c4:37:57:dd:fb:14:b0:cd:d9 - 92:7a:52:da:ea:b1:3e:a5:81:11:24:e9:7e:cd:d2:d2 - ab:40:2d:64:4d:86:4e:9d:80:eb:48:e4:f8:0b:ab:00 - b7:10:aa:17:4d:53:d6:15:f2:d0:95:84:54:e2:74:ee - 3d:19:7a:93:9a:e0:09:03:3b:ee:48:0a:08:a8:21:6e - 53:54:d6:7a:23:50:08:72:37:72:5c:f1:50:06:5c:a8 - 30:71:4d:95:2f:6e:43:f7:40:ae:ef:d0:86:f9:73:b1 - e2:46:9e:64:98:ee:02:42:e5:20:cb:c9:93:d5:16:61 - 68:ca:7d:a8:8c:b7:1c:a5:0d:10:17:58:c2:18:f8:e7 - 01: - -public exponent: - 01:00:01: - -private exponent: - 44:b8:a9:7e:b5:3b:cd:51:51:bb:ef:af:4b:63:d5:9e - 5f:01:ff:b6:00:4f:e2:a0:42:76:c3:eb:03:a9:5a:c3 - 01:2a:6e:18:6f:56:b8:cb:94:98:3b:55:4f:3a:2b:bc - 2a:5d:9a:8d:d1:79:d3:24:5f:c4:c9:95:c6:3a:6d:2b - 22:56:e8:9f:66:98:81:ce:81:eb:b9:2d:f0:73:41:20 - b7:40:50:51:7e:30:58:0b:75:09:23:b1:73:dc:9e:ac - 79:a5:e5:48:5f:ee:ca:ec:39:19:1c:aa:0d:de:40:d7 - 08:90:db:c6:67:8f:55:bf:81:be:5b:8a:15:f8:e9:e6 - ac:82:2a:0b:c3:45:fe:3b:15:04:8c:c9:fa:37:cc:0c - 71:b0:db:9c:d2:5c:df:9f:55:18:20:a0:4b:eb:53:c9 - b9:1f:0a:a8:98:9e:10:5a:35:68:a1:41:43:4e:a3:5f - e3:8c:22:94:55:2f:80:98:b4:a6:a9:9b:b2:d8:72:e1 - 55:5e:1c:06:d3:39:ec:c9:11:c0:6e:30:51:66:c4:47 - f2:ad:e1:30:83:0e:6e:c3:15:6b:26:97:b2:d4:2c:6a - 7b:c7:d9:33:5c:ca:24:ab:a8:dc:3b:1b:46:25:35:3d - fa:21:fe:ad:e7:a4:c4:58:eb:d8:48:c4:6a:e6:d3:ae - b2:f1:c6:b1:ed:7b:ca:23:fc:51:bc:9f:3a:3e:56:9e - 6d:23:b0:e5:9a:46:ae:f9:ee:8f:1a:5e:cc:42:37:22 - 6e:4b:52:37:0d:36:59:72:1a:19:c1:17:f9:45:55:bd - 33:ce:15:5b:b5:ee:a3:88:a2:b8:46:fe:b8:e2:93:54 - 0a:68:81:a3:b8:63:95:c6:b2:96:d0:f8:ac:d6:5d:df - 97:58:85:6f:eb:2d:66:3b:27:7a:db:2c:7d:8d:04:01 - 44:af:4a:91:2e:e8:f7:6c:e8:52:7a:70:a4:31:9a:f0 - b7:d3:bc:dd:f4:46:d0:61:f9:5f:57:98:c0:79:b9:e1 - 0f:6e:76:33:66:46:82:7a:31:61:a4:88:19:48:15:86 - 0e:b1:40:6a:af:1a:3f:75:db:12:bc:79:c7:f2:fe:41 - 49:02:88:3b:66:33:44:12:9d:43:f4:e6:cf:0d:4d:c5 - 44:d4:a9:4e:0d:fe:dd:00:87:bf:5e:f2:aa:5f:71:cf - f8:4a:24:4c:2c:bf:ee:88:3d:f5:78:e1:5c:95:a7:4c - 82:c8:65:7d:e7:b3:d7:98:b2:f2:4b:66:5c:1c:f7:d5 - 6a:6e:af:a7:e9:98:31:e8:92:99:1d:64:85:fb:8a:f6 - 6c:3b:a1:dc:22:be:fc:f8:8c:3b:51:c4:c0:dc:44:71 - - -prime1: - 00:cb:37:46:9c:11:91:72:90:0e:18:7a:ad:d9:c4:cb - 3d:64:2c:47:9a:2f:04:69:05:d5:22:b4:f0:af:3e:30 - 26:2e:32:35:83:ba:e1:80:3b:5a:5d:1c:c9:ac:c5:85 - f2:87:93:8e:f3:dd:7c:f5:cd:a5:77:8b:c2:bf:47:67 - 4d:c7:df:49:cd:41:bf:6c:14:f8:95:0e:db:00:70:d7 - 3a:43:b8:ae:43:be:a3:ce:48:5d:1b:c5:30:14:34:91 - 2c:6e:c5:00:81:ab:86:4b:9f:f0:ab:45:27:62:e0:ea - 2d:89:61:a2:bd:06:4d:2e:c7:2c:9d:bd:fd:f5:39:b8 - 29:2a:f4:2e:bf:1f:13:9d:c8:d0:bb:56:67:d3:de:2d - 31:63:22:61:5c:30:14:ea:b7:72:a8:f4:ea:d6:db:0b - 35:86:a6:e4:8d:c7:c2:9f:2b:33:85:d7:05:45:f7:05 - d4:5a:74:8f:26:4a:91:b3:7a:cc:8d:57:1b:c2:a8:b8 - e8:b5:ef:b0:24:4d:1d:8d:03:a1:79:0b:d4:0b:06:1a - 62:ca:5c:b8:aa:f9:ff:f4:cb:54:e1:6e:a5:d7:41:46 - 7c:2d:36:4b:15:01:c9:17:45:3c:33:44:79:4c:99:b0 - 43:0d:f7:c0:05:13:e5:82:3d:53:a3:2b:88:d9:ee:3b - 83: - -prime2: - 00:cf:c3:9b:a6:b3:b2:65:0b:88:8c:64:db:1e:00:21 - 42:a5:bd:47:75:0d:a4:29:e5:e9:9b:b7:1a:cf:df:9f - a3:44:9c:67:a4:a9:0c:87:97:94:94:40:54:44:3b:bf - 1a:f3:71:97:05:d6:a5:ad:28:f5:a9:94:92:f5:7f:f8 - cd:82:40:6e:ca:ff:d8:3f:18:84:42:83:dd:9e:87:bf - fa:2e:96:5e:fc:1d:db:d4:d5:dd:2d:c3:71:f4:bf:54 - 6d:06:21:95:8f:0a:89:b8:14:62:7c:a7:9b:10:fc:ae - 4b:0c:51:ac:46:5a:ee:04:cc:e2:ad:fc:97:50:32:b9 - c7:1e:40:6f:07:de:45:7f:2e:ac:12:39:30:b8:7b:6d - 0f:07:75:bd:58:c2:e2:e7:52:2a:6f:7e:8a:2f:07:c5 - eb:10:db:8d:10:2e:d4:a2:1c:28:ca:f5:c4:de:56:6a - 20:f3:47:21:b7:4c:b6:04:93:86:e6:78:b5:89:64:cb - 9e:f2:cd:78:e7:43:e6:a0:94:41:b4:c5:a8:c7:43:2b - 20:17:50:7a:a9:c0:ee:db:9a:f0:48:85:1f:1f:64:11 - 4e:4d:dd:99:6f:49:7a:f2:65:2a:83:98:96:5b:a3:7e - 64:da:77:00:7e:4b:7a:55:d9:cf:d7:73:fb:63:1a:f8 - 2b: - -coefficient: - 69:74:f7:c6:34:ec:d8:f9:08:c2:d8:5b:6a:61:4e:33 - b3:9b:dd:f1:30:8d:2e:bf:12:2d:74:de:53:6c:d2:88 - e5:9f:5d:5f:f1:e3:59:00:c4:1d:e3:5a:1d:07:6a:53 - ea:c8:46:0e:b1:61:5d:03:27:d9:1a:67:54:8d:bb:85 - 53:07:39:19:56:8a:dd:9f:f4:30:13:fa:2e:ce:1c:8e - 1c:cb:d6:54:b7:a9:92:26:87:f6:60:c1:04:41:fc:7c - f7:fd:fb:a7:fc:f2:d0:2c:4a:65:ef:62:5b:f9:b4:d6 - e8:f6:68:57:1f:b2:d1:d6:c4:78:0e:d4:66:39:cd:4c - 42:d7:5b:d1:ce:ae:5a:b6:25:12:7f:d1:2c:ae:95:4c - 14:cb:0a:31:7c:a8:0a:71:b8:98:c8:2b:03:19:92:d0 - e1:4c:a8:c4:0b:8c:b9:8b:d0:9a:b3:8f:27:b3:a4:09 - 8b:6d:95:e3:12:a0:60:e6:e7:b9:8f:53:fa:b4:56:f1 - cd:fe:11:31:20:01:fd:82:73:f1:51:ba:13:71:f1:0f - 8b:78:3f:36:71:7e:15:93:35:01:06:8b:40:a3:b5:23 - 22:9d:c7:c7:88:c8:25:07:dd:0d:1d:70:43:37:3e:ea - 25:b0:54:f0:5d:83:29:ca:5a:ec:4e:f5:a7:66:f0:fa - - -exp1: - 4a:04:cd:3c:45:8d:e3:db:a2:b6:b9:e0:9f:04:76:3e - db:40:e1:a7:c0:5e:6b:de:8a:fe:84:47:72:9d:45:2e - 72:ff:28:cc:dd:82:0b:92:12:dc:fd:82:5b:e2:ea:62 - 27:8d:d0:b0:f4:c8:f2:43:40:74:e5:bc:3e:ad:c4:6b - e9:54:64:6e:55:f7:62:67:d5:0f:7e:04:b9:09:60:eb - c1:05:00:bc:7e:30:ee:0f:1f:92:e0:e5:1d:46:f4:65 - e9:c6:e9:e3:51:55:ae:30:08:9a:69:aa:e9:f2:20:7a - 0b:a3:3b:82:7c:4c:1a:b0:c3:88:85:4e:7e:46:d2:d4 - 73:e7:d3:2b:1c:27:a9:fe:1e:41:4e:3c:ad:48:2c:cf - e3:5a:ff:79:73:ad:fa:bc:6d:10:2b:7d:6a:5b:08:9f - 2b:77:98:a2:27:d3:b4:e4:28:75:24:97:b0:1f:44:c9 - 4f:55:4b:5a:d8:28:6f:e6:57:a1:57:cc:2d:c0:04:f2 - 06:6a:d2:8e:b6:64:00:1c:05:71:b0:a4:40:8b:ad:8a - b4:48:c7:9e:c7:46:ba:a4:61:3b:67:71:12:91:9d:19 - d7:e2:01:c1:1a:10:63:e0:7d:07:f3:75:f7:37:b7:a3 - 04:f0:6b:c9:ad:b0:98:1a:bc:5f:1f:99:4e:3f:de:ff - - -exp2: - 0d:a2:8b:bb:83:fd:88:2e:1a:97:04:23:71:33:96:fb - 35:bf:57:4a:32:4b:fc:c7:ee:ed:de:35:6f:41:00:cc - 09:3b:ae:7d:9a:ee:8c:93:81:17:bd:a5:0a:19:55:b0 - 62:1b:a9:4a:a3:cc:99:b1:9f:75:b2:9f:76:67:20:9f - f4:15:60:70:08:1c:5b:ff:b2:e6:5e:9b:13:c5:5a:ef - 03:51:b1:08:20:b9:85:9d:47:77:b2:64:ef:28:03:55 - 68:5a:99:e3:1a:50:f1:78:bd:01:eb:49:fc:f2:68:49 - da:94:1d:97:3c:6e:74:78:31:c4:33:58:86:d5:dd:65 - 58:f1:e7:97:7f:99:d5:ff:ed:21:01:09:d6:81:9b:25 - aa:5a:aa:c3:81:7e:bc:a9:a2:c9:50:67:a7:30:7e:67 - af:e2:88:be:70:24:5a:43:38:d6:21:0c:fb:7e:76:56 - 95:40:ac:d0:c7:c3:06:47:dc:49:91:d0:70:24:e2:4c - 1b:29:2a:ef:1a:80:af:37:2b:9c:be:80:16:1b:ad:5f - dc:c7:d6:54:ff:a9:6d:56:1c:c0:d5:a3:b6:3e:ad:f8 - 12:9a:21:70:b1:44:d5:55:98:55:ac:94:e9:8c:b0:45 - d4:24:8d:2e:bc:ab:59:a9:02:bf:e4:07:b2:78:59:a3 - - - -Public Key ID: - sha256:99:0C:C5:EF:0C:3C:DC:71:32:9D:02:23:F8:28:E8:3B:55:D8:00:3D:D0:12:F3:56:33:03:B1:FD:30:3B:71:F1 - sha1:37:0D:50:BB:93:90:C0:E5:78:D6:E1:D0:FE:99:64:6D:84:5E:3C:36 -Public key's random art: -+--[ RSA 4096]----+ -| ...+oo o | -| .+ *.o. E | -| . *.=. = o | -| o ..=+ o | -| S *+.+ | -| . o+ | -| | -| | -| | -+-----------------+ - ------BEGIN RSA PRIVATE KEY----- -MIIJJwIBAAKCAgEApOz4wcdTiEdjO192j39Q1EhzLq9qqZghpWUxQHSV0/Fpm7kU -j/wtK1xky130+H2ab+k/Sp8jjda0TVV7es7yXYQM2iYEDIc1i3oCtygFkISFpzNG -5SGLV2GANovwYfgPhvpnGo9inAVJlXv2DJouLBxsL3c86dA3auMjcJ8FNgfvSJ7S -o7vEdoZQo1QGKpToUUVPD9e+K/y+j4WI8swP8lJaZ/EIDK74ESAyyk7R+jYjItF4 -GrwR7RM7CxWHOENwO/3pxbiJF+oWXOt+2jsTTKyL1FZJHI2beBcPvQql9baBY/q0 -koT7cS8PJiGI/D6TqXQ25XkbTG/ldmzJ7K5toLJ+7iUcdt+q8sF0owS4hR/3VUXM -24DgBduzvmihD9G0k0wDNmjHu3te8tGQogvMJRTUtiw1AEO2DiUUZqd6p/pFGrJf -RyxU6nHBvr1ADeClBwcQB0hH2mVD59affexzBsUwhouk3v0fTgNQpX2/qfeKwVbn -V4bEN1fd+xSwzdmSelLa6rE+pYERJOl+zdLSq0AtZE2GTp2A60jk+AurALcQqhdN -U9YV8tCVhFTidO49GXqTmuAJAzvuSAoIqCFuU1TWeiNQCHI3clzxUAZcqDBxTZUv -bkP3QK7v0Ib5c7HiRp5kmO4CQuUgy8mT1RZhaMp9qIy3HKUNEBdYwhj45wECAwEA -AQKCAgBEuKl+tTvNUVG7769LY9WeXwH/tgBP4qBCdsPrA6lawwEqbhhvVrjLlJg7 -VU86K7wqXZqN0XnTJF/EyZXGOm0rIlbon2aYgc6B67kt8HNBILdAUFF+MFgLdQkj -sXPcnqx5peVIX+7K7DkZHKoN3kDXCJDbxmePVb+BvluKFfjp5qyCKgvDRf47FQSM -yfo3zAxxsNuc0lzfn1UYIKBL61PJuR8KqJieEFo1aKFBQ06jX+OMIpRVL4CYtKap -m7LYcuFVXhwG0znsyRHAbjBRZsRH8q3hMIMObsMVayaXstQsanvH2TNcyiSrqNw7 -G0YlNT36If6t56TEWOvYSMRq5tOusvHGse17yiP8UbyfOj5Wnm0jsOWaRq757o8a -XsxCNyJuS1I3DTZZchoZwRf5RVW9M84VW7Xuo4iiuEb+uOKTVApogaO4Y5XGspbQ -+KzWXd+XWIVv6y1mOyd62yx9jQQBRK9KkS7o92zoUnpwpDGa8LfTvN30RtBh+V9X -mMB5ueEPbnYzZkaCejFhpIgZSBWGDrFAaq8aP3XbErx5x/L+QUkCiDtmM0QSnUP0 -5s8NTcVE1KlODf7dAIe/XvKqX3HP+EokTCy/7og99XjhXJWnTILIZX3ns9eYsvJL -Zlwc99Vqbq+n6Zgx6JKZHWSF+4r2bDuh3CK+/PiMO1HEwNxEcQKCAQEAyzdGnBGR -cpAOGHqt2cTLPWQsR5ovBGkF1SK08K8+MCYuMjWDuuGAO1pdHMmsxYXyh5OO8918 -9c2ld4vCv0dnTcffSc1Bv2wU+JUO2wBw1zpDuK5DvqPOSF0bxTAUNJEsbsUAgauG -S5/wq0UnYuDqLYlhor0GTS7HLJ29/fU5uCkq9C6/HxOdyNC7VmfT3i0xYyJhXDAU -6rdyqPTq1tsLNYam5I3Hwp8rM4XXBUX3BdRadI8mSpGzesyNVxvCqLjote+wJE0d -jQOheQvUCwYaYspcuKr5//TLVOFupddBRnwtNksVAckXRTwzRHlMmbBDDffABRPl -gj1ToyuI2e47gwKCAQEAz8ObprOyZQuIjGTbHgAhQqW9R3UNpCnl6Zu3Gs/fn6NE -nGekqQyHl5SUQFREO78a83GXBdalrSj1qZSS9X/4zYJAbsr/2D8YhEKD3Z6Hv/ou -ll78HdvU1d0tw3H0v1RtBiGVjwqJuBRifKebEPyuSwxRrEZa7gTM4q38l1Ayucce -QG8H3kV/LqwSOTC4e20PB3W9WMLi51Iqb36KLwfF6xDbjRAu1KIcKMr1xN5WaiDz -RyG3TLYEk4bmeLWJZMue8s1450PmoJRBtMWox0MrIBdQeqnA7tua8EiFHx9kEU5N -3ZlvSXryZSqDmJZbo35k2ncAfkt6VdnP13P7Yxr4KwKCAQBKBM08RY3j26K2ueCf -BHY+20Dhp8Bea96K/oRHcp1FLnL/KMzdgguSEtz9glvi6mInjdCw9MjyQ0B05bw+ -rcRr6VRkblX3YmfVD34EuQlg68EFALx+MO4PH5Lg5R1G9GXpxunjUVWuMAiaaarp -8iB6C6M7gnxMGrDDiIVOfkbS1HPn0yscJ6n+HkFOPK1ILM/jWv95c636vG0QK31q -WwifK3eYoifTtOQodSSXsB9EyU9VS1rYKG/mV6FXzC3ABPIGatKOtmQAHAVxsKRA -i62KtEjHnsdGuqRhO2dxEpGdGdfiAcEaEGPgfQfzdfc3t6ME8GvJrbCYGrxfH5lO -P97/AoIBAA2ii7uD/YguGpcEI3Ezlvs1v1dKMkv8x+7t3jVvQQDMCTuufZrujJOB -F72lChlVsGIbqUqjzJmxn3Wyn3ZnIJ/0FWBwCBxb/7LmXpsTxVrvA1GxCCC5hZ1H -d7Jk7ygDVWhameMaUPF4vQHrSfzyaEnalB2XPG50eDHEM1iG1d1lWPHnl3+Z1f/t -IQEJ1oGbJapaqsOBfryposlQZ6cwfmev4oi+cCRaQzjWIQz7fnZWlUCs0MfDBkfc -SZHQcCTiTBspKu8agK83K5y+gBYbrV/cx9ZU/6ltVhzA1aO2Pq34EpohcLFE1VWY -VayU6YywRdQkjS68q1mpAr/kB7J4WaMCggEAaXT3xjTs2PkIwthbamFOM7Ob3fEw -jS6/Ei103lNs0ojln11f8eNZAMQd41odB2pT6shGDrFhXQMn2RpnVI27hVMHORlW -it2f9DAT+i7OHI4cy9ZUt6mSJof2YMEEQfx89/37p/zy0CxKZe9iW/m01uj2aFcf -stHWxHgO1GY5zUxC11vRzq5atiUSf9EsrpVMFMsKMXyoCnG4mMgrAxmS0OFMqMQL -jLmL0JqzjyezpAmLbZXjEqBg5ue5j1P6tFbxzf4RMSAB/YJz8VG6E3HxD4t4PzZx -fhWTNQEGi0CjtSMincfHiMglB90NHXBDNz7qJbBU8F2DKcpa7E71p2bw+g== ------END RSA PRIVATE KEY----- diff --git a/test/test_certs/server.cert.pem b/test/test_certs/server.cert.pem deleted file mode 100644 index 32f9d38dd..000000000 --- a/test/test_certs/server.cert.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIMWId0/ykDlohr/6M4MA0GCSqGSIb3DQEBCwUAMHQxFTAT -BgNVBAMTDGxvY2FsaG9zdCBDQTEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBGYWN0 -b3J5MRIwEAYDVQQHDAlHw7Z0ZWJvcmcxGjAYBgNVBAgMEVbDpHN0cmEgR8O2dGFs -YW5kMQswCQYDVQQGEwJTRTAeFw0xNzAxMjQxNTM4MzlaFw0xODAxMjQxNTM4Mzla -MDQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBG -YWN0b3J5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzhorPKY3cEsy -vbKPvLn6aA3RArbRAgDMR+a4knyMWpQ8Nhb+xgLG/CUAO/fHhuyltw6vg7dx3+X+ -5jXMc7dfZf2YNGZTAn7jAlpTYPJMMO5m/VCOaiDdymU41YHYuV64pmKCrDv5EeHE -Fi7A2LuCXfsn+jIV6iPs6ASbgCavVs8yVp2Sb4qrrXVlgBuOv5ety369mNi91Yot -wZ8jbI1qAAe/nujrJwshg1qSTGTTseaZ0aAgimuoE+BsMB59VL9fVUEBH+dn7Qp/ -ff/AQpwjJK7hrCL9177bLMp23t6uyGkZlqEqlJ1+1ZTP4fl9ZOPmGhZE6laohcLx -kI0VThH8jmoOp7nQWWkjzAqj28qSYfbXd975pw3ZdztqTt3vgVmvWLWJfOfXcJ4+ -+OUu9Boocsirxx53p4ONjz+a5BQ7RI0dDEx0yKmrzDTmuBiLqeZPxQBwtYRBIw0g -sGCIp8jYGdlzBg39cETQ48BA0jwKdDO819+ZCh4ZlE707clz/GMR2v3DiGQTOac8 -vlagpmsWsmzNtGjLEZKKAlppx/OMoOKEi0LGA7Nos+Du6RzKYKEqkGwLW2gxj43A -FPjr9Pvr+sbUectDLZz3DvwXkrsBvz7ySObS+1LZWMgt5mvT+NSx5oHSaok4g/fZ -fBf2jHABuIHTkGiTlKaO3NjAwj3nEZkCAwEAAaN2MHQwDAYDVR0TAQH/BAIwADAT -BgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBQS -NmTiVjC6QIA8chtyQhUb4Se1wzAfBgNVHSMEGDAWgBSLOwbxgKwVtYYG1FO8XU1n -8CQL4DANBgkqhkiG9w0BAQsFAAOCAgEAj5V6MSHkRWAWUaEzMxVuyYMHhVqZenM2 -KBhaoM3Y9zbd5RHpYaypAKqr2joLGnNgIi9jsnCOOc+fdwh0lFd+faW5DvbPpIH/ -n1DlmnFv5mJ4uKuaRKhSY5NjPsL304riZKGjoi7fIuZgOv2lqXkvwB7e1b0/T4e4 -e9BjXuXLBkWSpPVo6TwdzJsQTRdr82uha+HxWs464Bo/Z/kgLaWTOQE3zpDXd5Yj -oC4sMlbIHb86hDQJrJwLmSpy5J2hIKyoQDhDRagxzULkDgrHUEhC4Ri9e2hgeZbX -/Na3AJw6EBnHP58yI6JpWX40B3HBGSJghzEUX+fijXbKPKLBjSD4rAj4kYNDkQim -SaRzU1MfFYp8MXMHTQw9cVH9wiLt1/HHWSfVZK5oTxjXFBnmV5INvMoPbHFrydy0 -a9aiOb9usJwOjwgBwnQFruvsEOl5xltBWHSGRC4iA28o8jlOPzR0XTijsUczrO08 -s10rTzuFNFymumGcMCoHWVFtQdK3Z3TSC1mvPeh5o/pHFXAO25YZjbMcx/cWcZq0 -Fg5CdQpkOZB+UJE7QBdN+xg++AxPsHihSp3RJTgZaTgaPL7eQXNCDHp3l+5QTWTX -39wPkU672ANEjG2+P2VfACVZQFmDXA3MNkLdPmGkJ78sohfkAP+qZEm6wAX7id9C -3glO5S3cYm4= ------END CERTIFICATE----- diff --git a/test/test_certs/server.crl.pem b/test/test_certs/server.crl.pem deleted file mode 100644 index c77e221b2..000000000 --- a/test/test_certs/server.crl.pem +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN X509 CRL----- -MIIC+zCB5AIBATANBgkqhkiG9w0BAQsFADB0MRUwEwYDVQQDEwxsb2NhbGhvc3Qg -Q0ExHjAcBgNVBAoMFUfDtnRlYm9yZyBCaXQgRmFjdG9yeTESMBAGA1UEBwwJR8O2 -dGVib3JnMRowGAYDVQQIDBFWw6RzdHJhIEfDtnRhbGFuZDELMAkGA1UEBhMCU0UX -DTE3MDEyNDE1MzgzOVoXDTE4MDEyNDE1MzgzOVowAKA6MDgwHwYDVR0jBBgwFoAU -izsG8YCsFbWGBtRTvF1NZ/AkC+AwFQYDVR0UBA4CDFiHdP8sH+YAO+X4pTANBgkq -hkiG9w0BAQsFAAOCAgEAhbN7sB2e4R/K0E1drwX1O5Uz9B2XiEt8aHO+LKcuGbsp -u6m+M0QwkxBPVdcG2bdpoN0Q5YZr/azRiiZz/FtmoxkFiLziP1nLm2uJ68QTrzk7 -61vqZcM+oOKhjPMkf2YORHBFbwVOm/JDobDFrHjbE7gCMQD93L1TW1mlYdoujSV9 -qpUHZkfwJ7tlaNtMMSaGrtRh8PCl+TpzMvTpG30LPZY/VHlHxrb3RQL05ReCoHsn -r2OHvzEh3E4/YjKjXKIVVFvZh4K6ljdPWgOeizGcp+LxHySe/HKgVr86ia2xKEqw -tbSGD1okRrDrRu0rmRhAYgLh8BoKeJXROOKcyogDc3ViMNGJwVBE62ZajL/HcjPs -NSSeaNoYt+njRUpOWF2RFj4pLDZQc2VNWTDiJR+ljvlBlqdZ/Raia9J76tU1MuAs -wDTTTZpOA7URMqgR0gUigOGB2kSsWthscTfc3viL7WXBuh7a+3lURwQf9t3TnZLH -HTNQNBgQS84Gyt8y8hOx5dlnxBoRr1+BZT90VlqE9Ccllrins74uK/pGVNyIvKNg -Fk6Wfo9iCanP4Usdg1qj8mVAye8Zga/q/6C9/mpQ9rSc6z/vcoQ0Fehd03msTHpz -++7djv8LKLsCd5TyCFsSCgAFxZL0qR6cp7S8K6kH7XH216TAag5Q+Zdl2j26law= ------END X509 CRL----- diff --git a/test/test_certs/server.key.pem b/test/test_certs/server.key.pem deleted file mode 100644 index e7688538f..000000000 --- a/test/test_certs/server.key.pem +++ /dev/null @@ -1,240 +0,0 @@ -Public Key Info: - Public Key Algorithm: RSA - Key Security Level: High (4096 bits) - -modulus: - 00:ce:1a:2b:3c:a6:37:70:4b:32:bd:b2:8f:bc:b9:fa - 68:0d:d1:02:b6:d1:02:00:cc:47:e6:b8:92:7c:8c:5a - 94:3c:36:16:fe:c6:02:c6:fc:25:00:3b:f7:c7:86:ec - a5:b7:0e:af:83:b7:71:df:e5:fe:e6:35:cc:73:b7:5f - 65:fd:98:34:66:53:02:7e:e3:02:5a:53:60:f2:4c:30 - ee:66:fd:50:8e:6a:20:dd:ca:65:38:d5:81:d8:b9:5e - b8:a6:62:82:ac:3b:f9:11:e1:c4:16:2e:c0:d8:bb:82 - 5d:fb:27:fa:32:15:ea:23:ec:e8:04:9b:80:26:af:56 - cf:32:56:9d:92:6f:8a:ab:ad:75:65:80:1b:8e:bf:97 - ad:cb:7e:bd:98:d8:bd:d5:8a:2d:c1:9f:23:6c:8d:6a - 00:07:bf:9e:e8:eb:27:0b:21:83:5a:92:4c:64:d3:b1 - e6:99:d1:a0:20:8a:6b:a8:13:e0:6c:30:1e:7d:54:bf - 5f:55:41:01:1f:e7:67:ed:0a:7f:7d:ff:c0:42:9c:23 - 24:ae:e1:ac:22:fd:d7:be:db:2c:ca:76:de:de:ae:c8 - 69:19:96:a1:2a:94:9d:7e:d5:94:cf:e1:f9:7d:64:e3 - e6:1a:16:44:ea:56:a8:85:c2:f1:90:8d:15:4e:11:fc - 8e:6a:0e:a7:b9:d0:59:69:23:cc:0a:a3:db:ca:92:61 - f6:d7:77:de:f9:a7:0d:d9:77:3b:6a:4e:dd:ef:81:59 - af:58:b5:89:7c:e7:d7:70:9e:3e:f8:e5:2e:f4:1a:28 - 72:c8:ab:c7:1e:77:a7:83:8d:8f:3f:9a:e4:14:3b:44 - 8d:1d:0c:4c:74:c8:a9:ab:cc:34:e6:b8:18:8b:a9:e6 - 4f:c5:00:70:b5:84:41:23:0d:20:b0:60:88:a7:c8:d8 - 19:d9:73:06:0d:fd:70:44:d0:e3:c0:40:d2:3c:0a:74 - 33:bc:d7:df:99:0a:1e:19:94:4e:f4:ed:c9:73:fc:63 - 11:da:fd:c3:88:64:13:39:a7:3c:be:56:a0:a6:6b:16 - b2:6c:cd:b4:68:cb:11:92:8a:02:5a:69:c7:f3:8c:a0 - e2:84:8b:42:c6:03:b3:68:b3:e0:ee:e9:1c:ca:60:a1 - 2a:90:6c:0b:5b:68:31:8f:8d:c0:14:f8:eb:f4:fb:eb - fa:c6:d4:79:cb:43:2d:9c:f7:0e:fc:17:92:bb:01:bf - 3e:f2:48:e6:d2:fb:52:d9:58:c8:2d:e6:6b:d3:f8:d4 - b1:e6:81:d2:6a:89:38:83:f7:d9:7c:17:f6:8c:70:01 - b8:81:d3:90:68:93:94:a6:8e:dc:d8:c0:c2:3d:e7:11 - 99: - -public exponent: - 01:00:01: - -private exponent: - 35:c7:94:52:ae:18:b7:3f:98:0c:f1:e4:19:dd:f9:9e - 5f:44:93:cd:ba:7a:28:55:25:c9:e9:8d:a7:dc:43:e2 - 7c:57:ac:e6:6c:2a:db:5b:7e:18:32:8d:b4:4d:db:c0 - 5e:25:01:dd:7f:e7:c2:01:d4:e2:a3:55:27:38:c9:36 - e6:d6:69:8d:ed:c9:2f:ec:f2:77:39:5c:89:bd:21:e3 - 83:92:44:0b:f5:5b:80:24:49:07:6c:87:06:53:50:89 - 24:39:7e:59:8d:08:b0:06:d6:50:e7:80:40:cd:a4:f5 - fb:0c:72:77:d1:4a:18:77:c0:0f:3b:b8:53:df:da:ea - 13:fb:90:00:5f:04:b3:49:7e:e1:ae:6d:02:71:b2:15 - 92:8d:0e:d2:2c:74:54:9d:4a:d5:44:3e:4d:1c:15:75 - ce:8f:cc:da:80:49:de:d8:ae:da:da:63:fe:c7:52:9b - 96:b5:a1:6f:fc:4e:9d:3c:1a:8a:80:7f:e4:3a:51:f9 - 81:58:85:ff:9a:35:7f:20:07:26:8c:ae:d1:72:c6:a4 - d2:d1:66:46:77:30:6d:c7:e9:47:27:7f:7e:61:e3:3f - b1:82:a1:a9:67:c0:35:11:6c:54:56:65:f0:b7:7b:d4 - 56:28:4b:71:4e:8d:88:e1:d6:80:de:9f:99:90:8d:f2 - ef:07:ae:db:65:fb:70:90:89:22:c6:de:6a:06:83:01 - 16:51:a0:bb:3b:d2:ed:98:cc:62:24:df:37:e0:98:06 - b3:88:1d:b9:c6:1c:d5:a7:18:e5:b7:3b:34:0f:63:90 - f1:ab:0b:3e:e6:b4:9a:54:69:41:f6:c5:ad:03:44:b2 - 48:48:66:55:a8:4b:0e:bb:43:48:53:a7:07:23:a6:06 - 64:46:b5:e3:9d:9b:b2:de:35:67:cd:e6:e1:39:0f:0d - 8b:c5:8b:68:bb:38:86:b7:a0:81:f4:b0:89:fa:6d:2d - 56:80:77:66:6d:32:02:1f:93:82:47:01:37:35:95:6d - 55:87:9c:75:cd:9a:f9:16:f4:09:3b:a8:60:47:5e:34 - 7f:6b:58:99:c8:db:fb:60:f5:dd:54:9c:27:42:5b:a7 - 4e:27:28:92:af:c6:76:56:c5:0f:db:73:1c:e0:06:bd - 4a:4f:75:37:19:39:9a:a7:7f:f2:8c:18:a4:25:77:c4 - e9:4a:3a:9d:3a:83:67:2b:bf:bd:c5:4d:bb:37:68:8e - 6f:a9:0d:67:d4:87:e9:59:02:61:60:e1:3f:b0:81:81 - 89:ba:29:4a:f0:37:4b:42:95:90:99:fd:7d:a9:30:31 - 33:ba:dc:86:6b:ce:29:98:8b:37:f2:72:b1:90:79:a5 - - -prime1: - 00:fc:59:48:15:f7:ed:1a:7d:0b:ba:2d:4e:b2:bd:75 - 10:a4:f3:68:2b:db:18:6a:b2:ff:93:c8:0d:c4:4c:55 - 99:58:02:ba:60:43:81:ee:09:14:e5:5a:38:91:37:6d - f6:b4:4b:b3:5e:be:9e:3a:43:27:a7:06:06:a1:ae:dd - 36:2f:8c:b1:17:1d:b5:55:bc:1a:05:cc:35:d2:03:25 - 18:97:f7:84:a4:2b:18:f9:50:67:ee:3f:c9:8c:d1:35 - 95:0e:9d:28:0b:f5:9d:00:cd:e5:e9:43:3f:8a:90:59 - c1:ae:a2:74:60:2b:c8:9c:50:9e:f8:5f:f2:87:13:36 - dd:8e:b3:d9:52:4c:26:40:7f:b6:52:81:9a:29:90:af - 85:ba:4a:9c:e6:38:4e:7d:9e:05:2e:e4:af:b1:a4:51 - 5d:fe:1f:ce:aa:b2:e2:b8:74:9f:ac:86:c7:ab:a9:5e - 5b:8a:33:49:c6:14:f1:10:0d:d6:02:55:6a:6a:57:92 - 5c:21:b3:c8:cf:b2:49:78:cb:23:47:d3:f5:5a:c9:ad - 2c:4d:18:9d:c4:2b:84:e0:91:d9:8b:99:ce:cd:02:cb - b0:c2:db:02:f5:23:a2:e7:d4:93:ed:40:72:eb:29:4b - 71:85:46:03:d3:f2:da:d3:f1:3f:89:b0:cf:7a:29:2b - 5f: - -prime2: - 00:d1:15:96:34:3b:7b:31:af:5d:07:83:c8:1b:20:50 - 33:51:3d:6a:8f:83:80:86:cc:1b:b2:d1:d2:63:58:d3 - 4b:6f:d6:6e:fa:18:06:f3:9f:0d:1c:c6:fe:2e:64:72 - b6:cd:8d:8e:93:e0:71:c6:8b:20:2b:49:32:99:70:e8 - 89:51:11:63:ad:53:ff:a7:2e:0f:3c:f8:e3:a0:63:16 - a2:12:44:65:1f:d4:e8:81:03:54:9f:e3:1e:c6:a8:68 - fa:fb:70:f7:ad:59:f9:e3:8e:f4:e4:e1:2e:c6:35:f5 - 43:8c:83:34:2d:f2:4f:ee:43:68:95:16:e9:51:f9:bc - 1c:6b:f3:6b:46:83:b2:7d:90:6a:cc:8a:cb:a8:31:dd - 64:cf:60:58:af:dd:82:9a:55:95:36:fa:cf:89:24:38 - ad:bd:75:02:28:83:8f:95:2d:95:9e:c5:44:d7:c5:80 - cb:53:74:82:12:9d:d6:1b:df:9f:6e:a8:6d:a4:99:1f - b9:b9:83:bb:c8:b9:f1:b1:9a:d1:a6:16:43:71:a8:27 - 4d:6d:c3:46:10:cd:be:1b:03:af:1c:67:9c:13:58:47 - 93:f2:07:eb:73:65:f0:b1:cd:10:11:5b:20:c6:2a:b4 - 9f:f0:52:53:0f:e2:8a:2a:e5:c1:d3:f8:4d:1f:66:5e - 07: - -coefficient: - 0b:81:01:5e:44:50:b1:e2:3f:99:45:3a:6f:97:ae:f9 - b3:e9:fd:bd:d1:3e:f0:82:30:69:d7:99:89:fa:75:e8 - e9:64:f2:76:6e:72:3d:81:c1:bf:5a:21:63:06:71:e3 - 28:92:bd:58:58:35:b5:d3:65:5a:79:89:01:bd:09:70 - 2d:b8:89:c3:76:25:f0:41:d7:72:08:0b:59:e8:8f:4e - 22:fd:e9:97:a3:4a:13:40:de:1c:0f:fc:3f:87:a0:4f - ba:6e:41:cb:e7:df:04:67:90:14:3f:dd:18:33:01:6d - 3b:a8:ae:7c:5a:e2:23:e1:9c:2b:a0:d6:8f:d1:8b:8c - c8:ca:39:17:11:82:65:d3:a2:c0:05:d3:bd:c5:b0:17 - ea:62:a5:1a:fe:3e:c8:f0:fb:c6:5e:6b:60:50:a7:34 - f1:ff:a9:70:79:16:ce:9e:c0:15:34:b3:cc:e6:22:1b - 02:bb:b5:59:2a:b1:b8:82:f5:4d:b1:6b:a1:27:a9:1d - 4a:22:44:98:08:c3:7e:34:7a:01:56:27:29:20:80:10 - 27:10:b8:d1:ab:d1:d6:5f:69:80:68:27:74:96:41:7f - c1:91:3a:b1:25:c0:39:e9:58:f6:4a:36:3f:73:c9:ab - 39:7b:02:72:76:7a:2f:74:5a:63:93:b0:b2:52:d8:2c - - -exp1: - 00:af:90:01:f9:de:ba:23:b2:99:a5:16:7e:69:16:2c - 4a:bf:27:e4:f2:96:04:7f:bf:36:d6:7e:d9:2d:17:9a - 7a:0b:e4:21:fc:75:1b:01:1b:6a:61:42:8a:96:65:44 - e8:dd:78:c9:3b:02:4d:1c:e5:b9:c1:97:0c:a8:11:fb - 2f:06:97:d0:60:ef:b7:48:05:8c:e9:39:b0:bc:02:9a - 1d:69:24:b8:30:6b:17:7d:e4:b5:d9:e8:a0:f5:8a:5b - c7:ef:19:e1:51:a8:b8:69:65:d6:2d:9a:2e:ab:dd:4f - c4:d3:15:8c:f7:97:9e:83:3a:07:cf:6f:19:51:66:49 - c6:8c:d0:8d:42:97:5e:09:83:90:ba:08:16:d4:12:28 - 3b:56:67:30:8f:6e:df:14:c0:0a:85:1c:6f:2b:9c:d1 - 4b:1e:50:cc:bc:af:a5:d2:84:b8:ce:14:1a:f8:4f:e6 - 28:b3:96:89:1d:f9:55:d9:40:77:02:ba:a0:45:89:d5 - 76:a9:af:e5:e6:b1:f8:31:c4:ca:2f:df:c4:14:3c:b9 - 71:57:d5:e7:75:22:7d:d8:ab:3f:f9:c2:b1:40:aa:50 - 42:12:de:c2:49:00:59:07:07:3b:3f:55:96:5e:0b:25 - c4:bd:de:ff:2c:c7:09:fd:68:1b:37:55:3e:93:93:ee - eb: - -exp2: - 1d:39:2f:2d:4d:c3:02:46:c8:71:ec:71:63:99:38:9c - 73:96:69:f6:75:22:d9:b4:5d:8a:b8:d8:f7:19:cb:2e - 98:0b:18:a7:cc:03:ec:b9:26:54:07:5c:2f:8f:ca:98 - d2:52:1e:c3:7c:73:6a:94:b7:82:55:50:c5:31:8a:ce - e6:8d:cf:a8:c2:3c:d1:59:16:b8:26:f1:69:d1:6f:b0 - 67:6d:37:d5:23:24:fc:23:43:08:b5:ed:ef:46:4a:a6 - 09:ce:d8:4f:5a:6c:1a:ea:38:40:65:58:ec:4b:4e:64 - 8a:97:2c:0b:df:fa:5c:0a:ff:eb:16:b0:b5:cc:7a:06 - 5e:f0:3e:e6:34:32:16:6a:c1:02:a5:c3:a1:56:96:e3 - 28:39:07:2c:4d:81:9b:a0:aa:a2:6c:9b:e5:47:32:c4 - d5:de:2c:d7:0c:cc:5c:c5:6a:4c:2e:b6:58:92:3a:56 - 30:eb:ef:e2:3a:3d:5a:d7:fe:a8:b0:d1:e2:57:a4:9f - 88:d6:68:c0:bd:7c:92:9e:58:ff:25:cd:77:6d:51:50 - f9:2e:79:f4:8f:4d:2a:e9:70:e8:3f:73:09:21:25:a0 - 00:0c:2b:54:8d:c2:fe:96:f2:cf:fa:34:b2:7d:f5:91 - 05:3d:fc:6e:13:f3:cb:db:21:24:47:68:40:23:46:b1 - - - -Public Key ID: - sha256:01:06:99:B3:BD:7B:A9:83:6F:D4:D0:AF:49:52:0D:19:23:66:EC:EB:7E:8C:A7:E0:4A:C7:DC:A1:C0:98:9B:76 - sha1:12:36:64:E2:56:30:BA:40:80:3C:72:1B:72:42:15:1B:E1:27:B5:C3 -Public key's random art: -+--[ RSA 4096]----+ -|*o.=*o+ | -|*o=o=*. | -|o=o*oE+ | -| ..oo..o | -| . . S | -| . | -| | -| | -| | -+-----------------+ - ------BEGIN RSA PRIVATE KEY----- -MIIJKAIBAAKCAgEAzhorPKY3cEsyvbKPvLn6aA3RArbRAgDMR+a4knyMWpQ8Nhb+ -xgLG/CUAO/fHhuyltw6vg7dx3+X+5jXMc7dfZf2YNGZTAn7jAlpTYPJMMO5m/VCO -aiDdymU41YHYuV64pmKCrDv5EeHEFi7A2LuCXfsn+jIV6iPs6ASbgCavVs8yVp2S -b4qrrXVlgBuOv5ety369mNi91YotwZ8jbI1qAAe/nujrJwshg1qSTGTTseaZ0aAg -imuoE+BsMB59VL9fVUEBH+dn7Qp/ff/AQpwjJK7hrCL9177bLMp23t6uyGkZlqEq -lJ1+1ZTP4fl9ZOPmGhZE6laohcLxkI0VThH8jmoOp7nQWWkjzAqj28qSYfbXd975 -pw3ZdztqTt3vgVmvWLWJfOfXcJ4++OUu9Boocsirxx53p4ONjz+a5BQ7RI0dDEx0 -yKmrzDTmuBiLqeZPxQBwtYRBIw0gsGCIp8jYGdlzBg39cETQ48BA0jwKdDO819+Z -Ch4ZlE707clz/GMR2v3DiGQTOac8vlagpmsWsmzNtGjLEZKKAlppx/OMoOKEi0LG -A7Nos+Du6RzKYKEqkGwLW2gxj43AFPjr9Pvr+sbUectDLZz3DvwXkrsBvz7ySObS -+1LZWMgt5mvT+NSx5oHSaok4g/fZfBf2jHABuIHTkGiTlKaO3NjAwj3nEZkCAwEA -AQKCAgA1x5RSrhi3P5gM8eQZ3fmeX0STzbp6KFUlyemNp9xD4nxXrOZsKttbfhgy -jbRN28BeJQHdf+fCAdTio1UnOMk25tZpje3JL+zydzlcib0h44OSRAv1W4AkSQds -hwZTUIkkOX5ZjQiwBtZQ54BAzaT1+wxyd9FKGHfADzu4U9/a6hP7kABfBLNJfuGu -bQJxshWSjQ7SLHRUnUrVRD5NHBV1zo/M2oBJ3tiu2tpj/sdSm5a1oW/8Tp08GoqA -f+Q6UfmBWIX/mjV/IAcmjK7Rcsak0tFmRncwbcfpRyd/fmHjP7GCoalnwDURbFRW -ZfC3e9RWKEtxTo2I4daA3p+ZkI3y7weu22X7cJCJIsbeagaDARZRoLs70u2YzGIk -3zfgmAaziB25xhzVpxjltzs0D2OQ8asLPua0mlRpQfbFrQNEskhIZlWoSw67Q0hT -pwcjpgZkRrXjnZuy3jVnzebhOQ8Ni8WLaLs4hreggfSwifptLVaAd2ZtMgIfk4JH -ATc1lW1Vh5x1zZr5FvQJO6hgR140f2tYmcjb+2D13VScJ0Jbp04nKJKvxnZWxQ/b -cxzgBr1KT3U3GTmap3/yjBikJXfE6Uo6nTqDZyu/vcVNuzdojm+pDWfUh+lZAmFg -4T+wgYGJuilK8DdLQpWQmf19qTAxM7rchmvOKZiLN/JysZB5pQKCAQEA/FlIFfft -Gn0Lui1Osr11EKTzaCvbGGqy/5PIDcRMVZlYArpgQ4HuCRTlWjiRN232tEuzXr6e -OkMnpwYGoa7dNi+MsRcdtVW8GgXMNdIDJRiX94SkKxj5UGfuP8mM0TWVDp0oC/Wd -AM3l6UM/ipBZwa6idGAryJxQnvhf8ocTNt2Os9lSTCZAf7ZSgZopkK+Fukqc5jhO -fZ4FLuSvsaRRXf4fzqqy4rh0n6yGx6upXluKM0nGFPEQDdYCVWpqV5JcIbPIz7JJ -eMsjR9P1WsmtLE0YncQrhOCR2YuZzs0Cy7DC2wL1I6Ln1JPtQHLrKUtxhUYD0/La -0/E/ibDPeikrXwKCAQEA0RWWNDt7Ma9dB4PIGyBQM1E9ao+DgIbMG7LR0mNY00tv -1m76GAbznw0cxv4uZHK2zY2Ok+BxxosgK0kymXDoiVERY61T/6cuDzz446BjFqIS -RGUf1OiBA1Sf4x7GqGj6+3D3rVn544705OEuxjX1Q4yDNC3yT+5DaJUW6VH5vBxr -82tGg7J9kGrMisuoMd1kz2BYr92CmlWVNvrPiSQ4rb11AiiDj5UtlZ7FRNfFgMtT -dIISndYb359uqG2kmR+5uYO7yLnxsZrRphZDcagnTW3DRhDNvhsDrxxnnBNYR5Py -B+tzZfCxzRARWyDGKrSf8FJTD+KKKuXB0/hNH2ZeBwKCAQEAr5AB+d66I7KZpRZ+ -aRYsSr8n5PKWBH+/NtZ+2S0XmnoL5CH8dRsBG2phQoqWZUTo3XjJOwJNHOW5wZcM -qBH7LwaX0GDvt0gFjOk5sLwCmh1pJLgwaxd95LXZ6KD1ilvH7xnhUai4aWXWLZou -q91PxNMVjPeXnoM6B89vGVFmScaM0I1Cl14Jg5C6CBbUEig7Vmcwj27fFMAKhRxv -K5zRSx5QzLyvpdKEuM4UGvhP5iizlokd+VXZQHcCuqBFidV2qa/l5rH4McTKL9/E -FDy5cVfV53UifdirP/nCsUCqUEIS3sJJAFkHBzs/VZZeCyXEvd7/LMcJ/WgbN1U+ -k5Pu6wKCAQAdOS8tTcMCRshx7HFjmTicc5Zp9nUi2bRdirjY9xnLLpgLGKfMA+y5 -JlQHXC+PypjSUh7DfHNqlLeCVVDFMYrO5o3PqMI80VkWuCbxadFvsGdtN9UjJPwj -Qwi17e9GSqYJzthPWmwa6jhAZVjsS05kipcsC9/6XAr/6xawtcx6Bl7wPuY0MhZq -wQKlw6FWluMoOQcsTYGboKqibJvlRzLE1d4s1wzMXMVqTC62WJI6VjDr7+I6PVrX -/qiw0eJXpJ+I1mjAvXySnlj/Jc13bVFQ+S559I9NKulw6D9zCSEloAAMK1SNwv6W -8s/6NLJ99ZEFPfxuE/PL2yEkR2hAI0axAoIBAAuBAV5EULHiP5lFOm+Xrvmz6f29 -0T7wgjBp15mJ+nXo6WTydm5yPYHBv1ohYwZx4yiSvVhYNbXTZVp5iQG9CXAtuInD -diXwQddyCAtZ6I9OIv3pl6NKE0DeHA/8P4egT7puQcvn3wRnkBQ/3RgzAW07qK58 -WuIj4ZwroNaP0YuMyMo5FxGCZdOiwAXTvcWwF+pipRr+Psjw+8Zea2BQpzTx/6lw -eRbOnsAVNLPM5iIbAru1WSqxuIL1TbFroSepHUoiRJgIw340egFWJykggBAnELjR -q9HWX2mAaCd0lkF/wZE6sSXAOelY9ko2P3PJqzl7AnJ2ei90WmOTsLJS2Cw= ------END RSA PRIVATE KEY----- diff --git a/test/test_certs/test_client.cert.pem b/test/test_certs/test_client.cert.pem deleted file mode 100644 index 27838d74f..000000000 --- a/test/test_certs/test_client.cert.pem +++ /dev/null @@ -1,33 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIMWId1Agdxxug/oIa6MA0GCSqGSIb3DQEBCwUAMHQxFTAT -BgNVBAMTDGxvY2FsaG9zdCBDQTEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBGYWN0 -b3J5MRIwEAYDVQQHDAlHw7Z0ZWJvcmcxGjAYBgNVBAgMEVbDpHN0cmEgR8O2dGFs -YW5kMQswCQYDVQQGEwJTRTAeFw0xNzAxMjQxNTM4NDJaFw0xODAxMjQxNTM4NDJa -MDQxEjAQBgNVBAMTCWxvY2FsaG9zdDEeMBwGA1UECgwVR8O2dGVib3JnIEJpdCBG -YWN0b3J5MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApOz4wcdTiEdj -O192j39Q1EhzLq9qqZghpWUxQHSV0/Fpm7kUj/wtK1xky130+H2ab+k/Sp8jjda0 -TVV7es7yXYQM2iYEDIc1i3oCtygFkISFpzNG5SGLV2GANovwYfgPhvpnGo9inAVJ -lXv2DJouLBxsL3c86dA3auMjcJ8FNgfvSJ7So7vEdoZQo1QGKpToUUVPD9e+K/y+ -j4WI8swP8lJaZ/EIDK74ESAyyk7R+jYjItF4GrwR7RM7CxWHOENwO/3pxbiJF+oW -XOt+2jsTTKyL1FZJHI2beBcPvQql9baBY/q0koT7cS8PJiGI/D6TqXQ25XkbTG/l -dmzJ7K5toLJ+7iUcdt+q8sF0owS4hR/3VUXM24DgBduzvmihD9G0k0wDNmjHu3te -8tGQogvMJRTUtiw1AEO2DiUUZqd6p/pFGrJfRyxU6nHBvr1ADeClBwcQB0hH2mVD -59affexzBsUwhouk3v0fTgNQpX2/qfeKwVbnV4bEN1fd+xSwzdmSelLa6rE+pYER -JOl+zdLSq0AtZE2GTp2A60jk+AurALcQqhdNU9YV8tCVhFTidO49GXqTmuAJAzvu -SAoIqCFuU1TWeiNQCHI3clzxUAZcqDBxTZUvbkP3QK7v0Ib5c7HiRp5kmO4CQuUg -y8mT1RZhaMp9qIy3HKUNEBdYwhj45wECAwEAAaN2MHQwDAYDVR0TAQH/BAIwADAT -BgNVHSUEDDAKBggrBgEFBQcDAjAPBgNVHQ8BAf8EBQMDB6AAMB0GA1UdDgQWBBQ3 -DVC7k5DA5XjW4dD+mWRthF48NjAfBgNVHSMEGDAWgBSLOwbxgKwVtYYG1FO8XU1n -8CQL4DANBgkqhkiG9w0BAQsFAAOCAgEASw6rojEtxVNLmqhuCGRXpBkeJcEC3hgd -usOmw3QFwMRwG0JkQuDTZobs20bWCSJzCnGcou6mvdOhjtaErZ/KUA2uAr9YvlmO -+F7mpXlGjfETm3Ta8JzvTtqgCPjQmz+Wn7nnFmwl9MLOOh//NXQ1KJd/ixWtpaLG -GhY3zduNczLT7O+qganFneostaZFpa/BL3PVXoI669zp7GmzJYSjuxQIyAMP/hKJ -aFfTNIv9WhPzXrzEviLspJ7OwfWW/S46WZWcXzauXiYS5y21rfFSBSAo1J3oRV95 -qi2IMM4PVpwQISMPwdJe3f2+Gc6qn3TaHFrnSv8OuRNOdvuqV9MLn9uxovgTTyw4 -j1sb7kO6OoLCqrbdZ+lTHy48jahpV3q+Vcep8WA+fC42sV/lfMuuS3EaQTJIfu89 -+R+pFQPco60risM8Gy/k0QkwxVwoxsq5xM+SaGyHpEazSasX/qJBj+VH97a9rlje -fzPpmApWRtZNMNx4qSx7ZEFVtSynM7vDWsWsB9x+OYdObzl4bbZUZ3MSCY0/smGO -PIbNPmqhlr8bRuJGGlXuftOGLPnssWrZSbQhMzBJQWGiKsHQF+AsdQzaDrtgwuWM -6REkiXd/cs0QXZh9P2ZzzIqKuIwwOazppJ5mdenWx/0X3Ex7PfdiGZPL8f8DEE7/ -lHXLHvTvWag= ------END CERTIFICATE----- diff --git a/test/test_certs/test_client.key.pem b/test/test_certs/test_client.key.pem deleted file mode 100644 index 6bcd83661..000000000 --- a/test/test_certs/test_client.key.pem +++ /dev/null @@ -1,240 +0,0 @@ -Public Key Info: - Public Key Algorithm: RSA - Key Security Level: High (4096 bits) - -modulus: - 00:a4:ec:f8:c1:c7:53:88:47:63:3b:5f:76:8f:7f:50 - d4:48:73:2e:af:6a:a9:98:21:a5:65:31:40:74:95:d3 - f1:69:9b:b9:14:8f:fc:2d:2b:5c:64:cb:5d:f4:f8:7d - 9a:6f:e9:3f:4a:9f:23:8d:d6:b4:4d:55:7b:7a:ce:f2 - 5d:84:0c:da:26:04:0c:87:35:8b:7a:02:b7:28:05:90 - 84:85:a7:33:46:e5:21:8b:57:61:80:36:8b:f0:61:f8 - 0f:86:fa:67:1a:8f:62:9c:05:49:95:7b:f6:0c:9a:2e - 2c:1c:6c:2f:77:3c:e9:d0:37:6a:e3:23:70:9f:05:36 - 07:ef:48:9e:d2:a3:bb:c4:76:86:50:a3:54:06:2a:94 - e8:51:45:4f:0f:d7:be:2b:fc:be:8f:85:88:f2:cc:0f - f2:52:5a:67:f1:08:0c:ae:f8:11:20:32:ca:4e:d1:fa - 36:23:22:d1:78:1a:bc:11:ed:13:3b:0b:15:87:38:43 - 70:3b:fd:e9:c5:b8:89:17:ea:16:5c:eb:7e:da:3b:13 - 4c:ac:8b:d4:56:49:1c:8d:9b:78:17:0f:bd:0a:a5:f5 - b6:81:63:fa:b4:92:84:fb:71:2f:0f:26:21:88:fc:3e - 93:a9:74:36:e5:79:1b:4c:6f:e5:76:6c:c9:ec:ae:6d - a0:b2:7e:ee:25:1c:76:df:aa:f2:c1:74:a3:04:b8:85 - 1f:f7:55:45:cc:db:80:e0:05:db:b3:be:68:a1:0f:d1 - b4:93:4c:03:36:68:c7:bb:7b:5e:f2:d1:90:a2:0b:cc - 25:14:d4:b6:2c:35:00:43:b6:0e:25:14:66:a7:7a:a7 - fa:45:1a:b2:5f:47:2c:54:ea:71:c1:be:bd:40:0d:e0 - a5:07:07:10:07:48:47:da:65:43:e7:d6:9f:7d:ec:73 - 06:c5:30:86:8b:a4:de:fd:1f:4e:03:50:a5:7d:bf:a9 - f7:8a:c1:56:e7:57:86:c4:37:57:dd:fb:14:b0:cd:d9 - 92:7a:52:da:ea:b1:3e:a5:81:11:24:e9:7e:cd:d2:d2 - ab:40:2d:64:4d:86:4e:9d:80:eb:48:e4:f8:0b:ab:00 - b7:10:aa:17:4d:53:d6:15:f2:d0:95:84:54:e2:74:ee - 3d:19:7a:93:9a:e0:09:03:3b:ee:48:0a:08:a8:21:6e - 53:54:d6:7a:23:50:08:72:37:72:5c:f1:50:06:5c:a8 - 30:71:4d:95:2f:6e:43:f7:40:ae:ef:d0:86:f9:73:b1 - e2:46:9e:64:98:ee:02:42:e5:20:cb:c9:93:d5:16:61 - 68:ca:7d:a8:8c:b7:1c:a5:0d:10:17:58:c2:18:f8:e7 - 01: - -public exponent: - 01:00:01: - -private exponent: - 44:b8:a9:7e:b5:3b:cd:51:51:bb:ef:af:4b:63:d5:9e - 5f:01:ff:b6:00:4f:e2:a0:42:76:c3:eb:03:a9:5a:c3 - 01:2a:6e:18:6f:56:b8:cb:94:98:3b:55:4f:3a:2b:bc - 2a:5d:9a:8d:d1:79:d3:24:5f:c4:c9:95:c6:3a:6d:2b - 22:56:e8:9f:66:98:81:ce:81:eb:b9:2d:f0:73:41:20 - b7:40:50:51:7e:30:58:0b:75:09:23:b1:73:dc:9e:ac - 79:a5:e5:48:5f:ee:ca:ec:39:19:1c:aa:0d:de:40:d7 - 08:90:db:c6:67:8f:55:bf:81:be:5b:8a:15:f8:e9:e6 - ac:82:2a:0b:c3:45:fe:3b:15:04:8c:c9:fa:37:cc:0c - 71:b0:db:9c:d2:5c:df:9f:55:18:20:a0:4b:eb:53:c9 - b9:1f:0a:a8:98:9e:10:5a:35:68:a1:41:43:4e:a3:5f - e3:8c:22:94:55:2f:80:98:b4:a6:a9:9b:b2:d8:72:e1 - 55:5e:1c:06:d3:39:ec:c9:11:c0:6e:30:51:66:c4:47 - f2:ad:e1:30:83:0e:6e:c3:15:6b:26:97:b2:d4:2c:6a - 7b:c7:d9:33:5c:ca:24:ab:a8:dc:3b:1b:46:25:35:3d - fa:21:fe:ad:e7:a4:c4:58:eb:d8:48:c4:6a:e6:d3:ae - b2:f1:c6:b1:ed:7b:ca:23:fc:51:bc:9f:3a:3e:56:9e - 6d:23:b0:e5:9a:46:ae:f9:ee:8f:1a:5e:cc:42:37:22 - 6e:4b:52:37:0d:36:59:72:1a:19:c1:17:f9:45:55:bd - 33:ce:15:5b:b5:ee:a3:88:a2:b8:46:fe:b8:e2:93:54 - 0a:68:81:a3:b8:63:95:c6:b2:96:d0:f8:ac:d6:5d:df - 97:58:85:6f:eb:2d:66:3b:27:7a:db:2c:7d:8d:04:01 - 44:af:4a:91:2e:e8:f7:6c:e8:52:7a:70:a4:31:9a:f0 - b7:d3:bc:dd:f4:46:d0:61:f9:5f:57:98:c0:79:b9:e1 - 0f:6e:76:33:66:46:82:7a:31:61:a4:88:19:48:15:86 - 0e:b1:40:6a:af:1a:3f:75:db:12:bc:79:c7:f2:fe:41 - 49:02:88:3b:66:33:44:12:9d:43:f4:e6:cf:0d:4d:c5 - 44:d4:a9:4e:0d:fe:dd:00:87:bf:5e:f2:aa:5f:71:cf - f8:4a:24:4c:2c:bf:ee:88:3d:f5:78:e1:5c:95:a7:4c - 82:c8:65:7d:e7:b3:d7:98:b2:f2:4b:66:5c:1c:f7:d5 - 6a:6e:af:a7:e9:98:31:e8:92:99:1d:64:85:fb:8a:f6 - 6c:3b:a1:dc:22:be:fc:f8:8c:3b:51:c4:c0:dc:44:71 - - -prime1: - 00:cb:37:46:9c:11:91:72:90:0e:18:7a:ad:d9:c4:cb - 3d:64:2c:47:9a:2f:04:69:05:d5:22:b4:f0:af:3e:30 - 26:2e:32:35:83:ba:e1:80:3b:5a:5d:1c:c9:ac:c5:85 - f2:87:93:8e:f3:dd:7c:f5:cd:a5:77:8b:c2:bf:47:67 - 4d:c7:df:49:cd:41:bf:6c:14:f8:95:0e:db:00:70:d7 - 3a:43:b8:ae:43:be:a3:ce:48:5d:1b:c5:30:14:34:91 - 2c:6e:c5:00:81:ab:86:4b:9f:f0:ab:45:27:62:e0:ea - 2d:89:61:a2:bd:06:4d:2e:c7:2c:9d:bd:fd:f5:39:b8 - 29:2a:f4:2e:bf:1f:13:9d:c8:d0:bb:56:67:d3:de:2d - 31:63:22:61:5c:30:14:ea:b7:72:a8:f4:ea:d6:db:0b - 35:86:a6:e4:8d:c7:c2:9f:2b:33:85:d7:05:45:f7:05 - d4:5a:74:8f:26:4a:91:b3:7a:cc:8d:57:1b:c2:a8:b8 - e8:b5:ef:b0:24:4d:1d:8d:03:a1:79:0b:d4:0b:06:1a - 62:ca:5c:b8:aa:f9:ff:f4:cb:54:e1:6e:a5:d7:41:46 - 7c:2d:36:4b:15:01:c9:17:45:3c:33:44:79:4c:99:b0 - 43:0d:f7:c0:05:13:e5:82:3d:53:a3:2b:88:d9:ee:3b - 83: - -prime2: - 00:cf:c3:9b:a6:b3:b2:65:0b:88:8c:64:db:1e:00:21 - 42:a5:bd:47:75:0d:a4:29:e5:e9:9b:b7:1a:cf:df:9f - a3:44:9c:67:a4:a9:0c:87:97:94:94:40:54:44:3b:bf - 1a:f3:71:97:05:d6:a5:ad:28:f5:a9:94:92:f5:7f:f8 - cd:82:40:6e:ca:ff:d8:3f:18:84:42:83:dd:9e:87:bf - fa:2e:96:5e:fc:1d:db:d4:d5:dd:2d:c3:71:f4:bf:54 - 6d:06:21:95:8f:0a:89:b8:14:62:7c:a7:9b:10:fc:ae - 4b:0c:51:ac:46:5a:ee:04:cc:e2:ad:fc:97:50:32:b9 - c7:1e:40:6f:07:de:45:7f:2e:ac:12:39:30:b8:7b:6d - 0f:07:75:bd:58:c2:e2:e7:52:2a:6f:7e:8a:2f:07:c5 - eb:10:db:8d:10:2e:d4:a2:1c:28:ca:f5:c4:de:56:6a - 20:f3:47:21:b7:4c:b6:04:93:86:e6:78:b5:89:64:cb - 9e:f2:cd:78:e7:43:e6:a0:94:41:b4:c5:a8:c7:43:2b - 20:17:50:7a:a9:c0:ee:db:9a:f0:48:85:1f:1f:64:11 - 4e:4d:dd:99:6f:49:7a:f2:65:2a:83:98:96:5b:a3:7e - 64:da:77:00:7e:4b:7a:55:d9:cf:d7:73:fb:63:1a:f8 - 2b: - -coefficient: - 69:74:f7:c6:34:ec:d8:f9:08:c2:d8:5b:6a:61:4e:33 - b3:9b:dd:f1:30:8d:2e:bf:12:2d:74:de:53:6c:d2:88 - e5:9f:5d:5f:f1:e3:59:00:c4:1d:e3:5a:1d:07:6a:53 - ea:c8:46:0e:b1:61:5d:03:27:d9:1a:67:54:8d:bb:85 - 53:07:39:19:56:8a:dd:9f:f4:30:13:fa:2e:ce:1c:8e - 1c:cb:d6:54:b7:a9:92:26:87:f6:60:c1:04:41:fc:7c - f7:fd:fb:a7:fc:f2:d0:2c:4a:65:ef:62:5b:f9:b4:d6 - e8:f6:68:57:1f:b2:d1:d6:c4:78:0e:d4:66:39:cd:4c - 42:d7:5b:d1:ce:ae:5a:b6:25:12:7f:d1:2c:ae:95:4c - 14:cb:0a:31:7c:a8:0a:71:b8:98:c8:2b:03:19:92:d0 - e1:4c:a8:c4:0b:8c:b9:8b:d0:9a:b3:8f:27:b3:a4:09 - 8b:6d:95:e3:12:a0:60:e6:e7:b9:8f:53:fa:b4:56:f1 - cd:fe:11:31:20:01:fd:82:73:f1:51:ba:13:71:f1:0f - 8b:78:3f:36:71:7e:15:93:35:01:06:8b:40:a3:b5:23 - 22:9d:c7:c7:88:c8:25:07:dd:0d:1d:70:43:37:3e:ea - 25:b0:54:f0:5d:83:29:ca:5a:ec:4e:f5:a7:66:f0:fa - - -exp1: - 4a:04:cd:3c:45:8d:e3:db:a2:b6:b9:e0:9f:04:76:3e - db:40:e1:a7:c0:5e:6b:de:8a:fe:84:47:72:9d:45:2e - 72:ff:28:cc:dd:82:0b:92:12:dc:fd:82:5b:e2:ea:62 - 27:8d:d0:b0:f4:c8:f2:43:40:74:e5:bc:3e:ad:c4:6b - e9:54:64:6e:55:f7:62:67:d5:0f:7e:04:b9:09:60:eb - c1:05:00:bc:7e:30:ee:0f:1f:92:e0:e5:1d:46:f4:65 - e9:c6:e9:e3:51:55:ae:30:08:9a:69:aa:e9:f2:20:7a - 0b:a3:3b:82:7c:4c:1a:b0:c3:88:85:4e:7e:46:d2:d4 - 73:e7:d3:2b:1c:27:a9:fe:1e:41:4e:3c:ad:48:2c:cf - e3:5a:ff:79:73:ad:fa:bc:6d:10:2b:7d:6a:5b:08:9f - 2b:77:98:a2:27:d3:b4:e4:28:75:24:97:b0:1f:44:c9 - 4f:55:4b:5a:d8:28:6f:e6:57:a1:57:cc:2d:c0:04:f2 - 06:6a:d2:8e:b6:64:00:1c:05:71:b0:a4:40:8b:ad:8a - b4:48:c7:9e:c7:46:ba:a4:61:3b:67:71:12:91:9d:19 - d7:e2:01:c1:1a:10:63:e0:7d:07:f3:75:f7:37:b7:a3 - 04:f0:6b:c9:ad:b0:98:1a:bc:5f:1f:99:4e:3f:de:ff - - -exp2: - 0d:a2:8b:bb:83:fd:88:2e:1a:97:04:23:71:33:96:fb - 35:bf:57:4a:32:4b:fc:c7:ee:ed:de:35:6f:41:00:cc - 09:3b:ae:7d:9a:ee:8c:93:81:17:bd:a5:0a:19:55:b0 - 62:1b:a9:4a:a3:cc:99:b1:9f:75:b2:9f:76:67:20:9f - f4:15:60:70:08:1c:5b:ff:b2:e6:5e:9b:13:c5:5a:ef - 03:51:b1:08:20:b9:85:9d:47:77:b2:64:ef:28:03:55 - 68:5a:99:e3:1a:50:f1:78:bd:01:eb:49:fc:f2:68:49 - da:94:1d:97:3c:6e:74:78:31:c4:33:58:86:d5:dd:65 - 58:f1:e7:97:7f:99:d5:ff:ed:21:01:09:d6:81:9b:25 - aa:5a:aa:c3:81:7e:bc:a9:a2:c9:50:67:a7:30:7e:67 - af:e2:88:be:70:24:5a:43:38:d6:21:0c:fb:7e:76:56 - 95:40:ac:d0:c7:c3:06:47:dc:49:91:d0:70:24:e2:4c - 1b:29:2a:ef:1a:80:af:37:2b:9c:be:80:16:1b:ad:5f - dc:c7:d6:54:ff:a9:6d:56:1c:c0:d5:a3:b6:3e:ad:f8 - 12:9a:21:70:b1:44:d5:55:98:55:ac:94:e9:8c:b0:45 - d4:24:8d:2e:bc:ab:59:a9:02:bf:e4:07:b2:78:59:a3 - - - -Public Key ID: - sha256:99:0C:C5:EF:0C:3C:DC:71:32:9D:02:23:F8:28:E8:3B:55:D8:00:3D:D0:12:F3:56:33:03:B1:FD:30:3B:71:F1 - sha1:37:0D:50:BB:93:90:C0:E5:78:D6:E1:D0:FE:99:64:6D:84:5E:3C:36 -Public key's random art: -+--[ RSA 4096]----+ -| ...+oo o | -| .+ *.o. E | -| . *.=. = o | -| o ..=+ o | -| S *+.+ | -| . o+ | -| | -| | -| | -+-----------------+ - ------BEGIN RSA PRIVATE KEY----- -MIIJJwIBAAKCAgEApOz4wcdTiEdjO192j39Q1EhzLq9qqZghpWUxQHSV0/Fpm7kU -j/wtK1xky130+H2ab+k/Sp8jjda0TVV7es7yXYQM2iYEDIc1i3oCtygFkISFpzNG -5SGLV2GANovwYfgPhvpnGo9inAVJlXv2DJouLBxsL3c86dA3auMjcJ8FNgfvSJ7S -o7vEdoZQo1QGKpToUUVPD9e+K/y+j4WI8swP8lJaZ/EIDK74ESAyyk7R+jYjItF4 -GrwR7RM7CxWHOENwO/3pxbiJF+oWXOt+2jsTTKyL1FZJHI2beBcPvQql9baBY/q0 -koT7cS8PJiGI/D6TqXQ25XkbTG/ldmzJ7K5toLJ+7iUcdt+q8sF0owS4hR/3VUXM -24DgBduzvmihD9G0k0wDNmjHu3te8tGQogvMJRTUtiw1AEO2DiUUZqd6p/pFGrJf -RyxU6nHBvr1ADeClBwcQB0hH2mVD59affexzBsUwhouk3v0fTgNQpX2/qfeKwVbn -V4bEN1fd+xSwzdmSelLa6rE+pYERJOl+zdLSq0AtZE2GTp2A60jk+AurALcQqhdN -U9YV8tCVhFTidO49GXqTmuAJAzvuSAoIqCFuU1TWeiNQCHI3clzxUAZcqDBxTZUv -bkP3QK7v0Ib5c7HiRp5kmO4CQuUgy8mT1RZhaMp9qIy3HKUNEBdYwhj45wECAwEA -AQKCAgBEuKl+tTvNUVG7769LY9WeXwH/tgBP4qBCdsPrA6lawwEqbhhvVrjLlJg7 -VU86K7wqXZqN0XnTJF/EyZXGOm0rIlbon2aYgc6B67kt8HNBILdAUFF+MFgLdQkj -sXPcnqx5peVIX+7K7DkZHKoN3kDXCJDbxmePVb+BvluKFfjp5qyCKgvDRf47FQSM -yfo3zAxxsNuc0lzfn1UYIKBL61PJuR8KqJieEFo1aKFBQ06jX+OMIpRVL4CYtKap -m7LYcuFVXhwG0znsyRHAbjBRZsRH8q3hMIMObsMVayaXstQsanvH2TNcyiSrqNw7 -G0YlNT36If6t56TEWOvYSMRq5tOusvHGse17yiP8UbyfOj5Wnm0jsOWaRq757o8a -XsxCNyJuS1I3DTZZchoZwRf5RVW9M84VW7Xuo4iiuEb+uOKTVApogaO4Y5XGspbQ -+KzWXd+XWIVv6y1mOyd62yx9jQQBRK9KkS7o92zoUnpwpDGa8LfTvN30RtBh+V9X -mMB5ueEPbnYzZkaCejFhpIgZSBWGDrFAaq8aP3XbErx5x/L+QUkCiDtmM0QSnUP0 -5s8NTcVE1KlODf7dAIe/XvKqX3HP+EokTCy/7og99XjhXJWnTILIZX3ns9eYsvJL -Zlwc99Vqbq+n6Zgx6JKZHWSF+4r2bDuh3CK+/PiMO1HEwNxEcQKCAQEAyzdGnBGR -cpAOGHqt2cTLPWQsR5ovBGkF1SK08K8+MCYuMjWDuuGAO1pdHMmsxYXyh5OO8918 -9c2ld4vCv0dnTcffSc1Bv2wU+JUO2wBw1zpDuK5DvqPOSF0bxTAUNJEsbsUAgauG -S5/wq0UnYuDqLYlhor0GTS7HLJ29/fU5uCkq9C6/HxOdyNC7VmfT3i0xYyJhXDAU -6rdyqPTq1tsLNYam5I3Hwp8rM4XXBUX3BdRadI8mSpGzesyNVxvCqLjote+wJE0d -jQOheQvUCwYaYspcuKr5//TLVOFupddBRnwtNksVAckXRTwzRHlMmbBDDffABRPl -gj1ToyuI2e47gwKCAQEAz8ObprOyZQuIjGTbHgAhQqW9R3UNpCnl6Zu3Gs/fn6NE -nGekqQyHl5SUQFREO78a83GXBdalrSj1qZSS9X/4zYJAbsr/2D8YhEKD3Z6Hv/ou -ll78HdvU1d0tw3H0v1RtBiGVjwqJuBRifKebEPyuSwxRrEZa7gTM4q38l1Ayucce -QG8H3kV/LqwSOTC4e20PB3W9WMLi51Iqb36KLwfF6xDbjRAu1KIcKMr1xN5WaiDz -RyG3TLYEk4bmeLWJZMue8s1450PmoJRBtMWox0MrIBdQeqnA7tua8EiFHx9kEU5N -3ZlvSXryZSqDmJZbo35k2ncAfkt6VdnP13P7Yxr4KwKCAQBKBM08RY3j26K2ueCf -BHY+20Dhp8Bea96K/oRHcp1FLnL/KMzdgguSEtz9glvi6mInjdCw9MjyQ0B05bw+ -rcRr6VRkblX3YmfVD34EuQlg68EFALx+MO4PH5Lg5R1G9GXpxunjUVWuMAiaaarp -8iB6C6M7gnxMGrDDiIVOfkbS1HPn0yscJ6n+HkFOPK1ILM/jWv95c636vG0QK31q -WwifK3eYoifTtOQodSSXsB9EyU9VS1rYKG/mV6FXzC3ABPIGatKOtmQAHAVxsKRA -i62KtEjHnsdGuqRhO2dxEpGdGdfiAcEaEGPgfQfzdfc3t6ME8GvJrbCYGrxfH5lO -P97/AoIBAA2ii7uD/YguGpcEI3Ezlvs1v1dKMkv8x+7t3jVvQQDMCTuufZrujJOB -F72lChlVsGIbqUqjzJmxn3Wyn3ZnIJ/0FWBwCBxb/7LmXpsTxVrvA1GxCCC5hZ1H -d7Jk7ygDVWhameMaUPF4vQHrSfzyaEnalB2XPG50eDHEM1iG1d1lWPHnl3+Z1f/t -IQEJ1oGbJapaqsOBfryposlQZ6cwfmev4oi+cCRaQzjWIQz7fnZWlUCs0MfDBkfc -SZHQcCTiTBspKu8agK83K5y+gBYbrV/cx9ZU/6ltVhzA1aO2Pq34EpohcLFE1VWY -VayU6YywRdQkjS68q1mpAr/kB7J4WaMCggEAaXT3xjTs2PkIwthbamFOM7Ob3fEw -jS6/Ei103lNs0ojln11f8eNZAMQd41odB2pT6shGDrFhXQMn2RpnVI27hVMHORlW -it2f9DAT+i7OHI4cy9ZUt6mSJof2YMEEQfx89/37p/zy0CxKZe9iW/m01uj2aFcf -stHWxHgO1GY5zUxC11vRzq5atiUSf9EsrpVMFMsKMXyoCnG4mMgrAxmS0OFMqMQL -jLmL0JqzjyezpAmLbZXjEqBg5ue5j1P6tFbxzf4RMSAB/YJz8VG6E3HxD4t4PzZx -fhWTNQEGi0CjtSMincfHiMglB90NHXBDNz7qJbBU8F2DKcpa7E71p2bw+g== ------END RSA PRIVATE KEY----- diff --git a/test/test_hooks/on-add-accept b/test/test_hooks/on-add-accept old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave1 b/test/test_hooks/on-add-misbehave1 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave2 b/test/test_hooks/on-add-misbehave2 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave3 b/test/test_hooks/on-add-misbehave3 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave4 b/test/test_hooks/on-add-misbehave4 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave5 b/test/test_hooks/on-add-misbehave5 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-misbehave6 b/test/test_hooks/on-add-misbehave6 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add-modify b/test/test_hooks/on-add-modify new file mode 100755 index 000000000..175332985 --- /dev/null +++ b/test/test_hooks/on-add-modify @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +# Input: +# - Line of JSON for proposed new task. +read new_task + +if (echo $new_task | grep -qE '[tT]eh'); +then + new_task=$(echo $new_task | sed -r 's/([tT])eh/\1he/g') +fi + +# Output: +# - JSON, modified +# - Optional feedback/error. +echo $new_task +echo 'FEEDBACK' + +# Status: +# - 0: JSON accepted, non-JSON is feedback. +# - non-0: JSON ignored, non-JSON is error. +exit 0 diff --git a/test/test_hooks/on-add-reject b/test/test_hooks/on-add-reject old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-add.dummy b/test/test_hooks/on-add.dummy old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-exit-bad b/test/test_hooks/on-exit-bad old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-exit-good b/test/test_hooks/on-exit-good old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-exit-misbehave1 b/test/test_hooks/on-exit-misbehave1 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-exit-misbehave2 b/test/test_hooks/on-exit-misbehave2 old mode 100644 new mode 100755 index 2b52b012b..338b21284 --- a/test/test_hooks/on-exit-misbehave2 +++ b/test/test_hooks/on-exit-misbehave2 @@ -15,4 +15,3 @@ echo 'FEEDBACK' # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 0 - diff --git a/test/test_hooks/on-exit.dummy b/test/test_hooks/on-exit.dummy old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-launch-bad b/test/test_hooks/on-launch-bad old mode 100644 new mode 100755 index 221a51262..01c7cbfdb --- a/test/test_hooks/on-launch-bad +++ b/test/test_hooks/on-launch-bad @@ -14,4 +14,3 @@ echo 'FEEDBACK' # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 1 - diff --git a/test/test_hooks/on-launch-good b/test/test_hooks/on-launch-good old mode 100644 new mode 100755 index e6c6226c2..1c1aefea6 --- a/test/test_hooks/on-launch-good +++ b/test/test_hooks/on-launch-good @@ -14,4 +14,3 @@ echo 'FEEDBACK' # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 0 - diff --git a/test/test_hooks/on-launch-good-env b/test/test_hooks/on-launch-good-env old mode 100644 new mode 100755 index 34a55cac5..ffbe3ca62 --- a/test/test_hooks/on-launch-good-env +++ b/test/test_hooks/on-launch-good-env @@ -23,4 +23,3 @@ echo $6 # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 0 - diff --git a/test/test_hooks/on-launch-misbehave1 b/test/test_hooks/on-launch-misbehave1 old mode 100644 new mode 100755 index d309739e4..096218612 --- a/test/test_hooks/on-launch-misbehave1 +++ b/test/test_hooks/on-launch-misbehave1 @@ -15,4 +15,3 @@ echo 'FEEDBACK' # - non-0: JSON ignored, non-JSON is error. kill -9 $$ exit 0 - diff --git a/test/test_hooks/on-launch-misbehave2 b/test/test_hooks/on-launch-misbehave2 old mode 100644 new mode 100755 index f981f8d68..7e46056aa --- a/test/test_hooks/on-launch-misbehave2 +++ b/test/test_hooks/on-launch-misbehave2 @@ -15,4 +15,3 @@ echo 'FEEDBACK' # - 0: JSON ignored, non-JSON is feedback. # - non-0: JSON ignored, non-JSON is error. exit 0 - diff --git a/test/test_hooks/on-launch.dummy b/test/test_hooks/on-launch.dummy old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-accept b/test/test_hooks/on-modify-accept old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-for-template-badexit.py b/test/test_hooks/on-modify-for-template-badexit.py old mode 100644 new mode 100755 index 6527ebfb3..06f62918c --- a/test/test_hooks/on-modify-for-template-badexit.py +++ b/test/test_hooks/on-modify-for-template-badexit.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import sys import json @@ -13,7 +12,7 @@ task["description"] = "This is an example modify hook" # A random message sys.stdout.write("Hello from the template hook\n") -sys.stdout.write(json.dumps(task, separators=(',', ':')) + '\n') +sys.stdout.write(json.dumps(task, separators=(",", ":")) + "\n") sys.exit(1) # vim: ai sts=4 et sw=4 diff --git a/test/test_hooks/on-modify-for-template.py b/test/test_hooks/on-modify-for-template.py old mode 100644 new mode 100755 index aeea80f2a..01cf35a12 --- a/test/test_hooks/on-modify-for-template.py +++ b/test/test_hooks/on-modify-for-template.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- import sys import json @@ -13,7 +12,7 @@ task["description"] = "This is an example modify hook" # A random message sys.stdout.write("Hello from the template hook\n") -sys.stdout.write(json.dumps(task, separators=(',', ':')) + '\n') +sys.stdout.write(json.dumps(task, separators=(",", ":")) + "\n") sys.exit(0) # vim: ai sts=4 et sw=4 diff --git a/test/test_hooks/on-modify-misbehave2 b/test/test_hooks/on-modify-misbehave2 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-misbehave3 b/test/test_hooks/on-modify-misbehave3 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-misbehave4 b/test/test_hooks/on-modify-misbehave4 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-misbehave5 b/test/test_hooks/on-modify-misbehave5 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-misbehave6 b/test/test_hooks/on-modify-misbehave6 old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-reject b/test/test_hooks/on-modify-reject old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify-revert b/test/test_hooks/on-modify-revert old mode 100644 new mode 100755 diff --git a/test/test_hooks/on-modify.dummy b/test/test_hooks/on-modify.dummy old mode 100644 new mode 100755 diff --git a/test/test_hooks/wrapper.sh b/test/test_hooks/wrapper.sh old mode 100644 new mode 100755 diff --git a/test/timesheet.t b/test/timesheet.test.py similarity index 93% rename from test/timesheet.t rename to test/timesheet.test.py index 2d0493401..2dc9af10d 100755 --- a/test/timesheet.t +++ b/test/timesheet.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -31,6 +30,7 @@ import re import sys import unittest from time import time + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -54,8 +54,8 @@ class TestTimesheet(TestCase): # C0 completed, this week # C1 completed, last week # C2 completed, 2wks ago - now = int(time()) - seven = now - 7 * 86400 + now = int(time()) + seven = now - 7 * 86400 fourteen = now - 14 * 86400 cls.t("add P0 entry:{0}".format(fourteen)) @@ -77,13 +77,17 @@ class TestTimesheet(TestCase): expected = re.compile( "Started.+PS2.+Completed.+C2.+" "Started.+PS1.+Completed.+C1.+" - "Started.+PS0.+Completed.+C0", re.DOTALL) + "Started.+PS0.+Completed.+C0", + re.DOTALL, + ) self.assertRegex(out, expected) def test_one_week(self): """One week of started and completed""" # This is the default filter, reduced from 4 weeks to 1. - code, out, err = self.t("timesheet (+PENDING and start.after:now-1wk) or (+COMPLETED and end.after:now-1wk)") + code, out, err = self.t( + "timesheet (+PENDING and start.after:now-1wk) or (+COMPLETED and end.after:now-1wk)" + ) expected = re.compile("Started.+PS0.+Completed.+C0", re.DOTALL) self.assertRegex(out, expected) @@ -92,8 +96,10 @@ class TestTimesheet(TestCase): self.assertNotIn("C1", out) self.assertNotIn("C2", out) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-1379.t b/test/tw-1379.test.py similarity index 96% rename from test/tw-1379.t rename to test/tw-1379.test.py index 98050fc57..f30cdee8b 100755 --- a/test/tw-1379.t +++ b/test/tw-1379.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -40,8 +40,8 @@ class TestBug1379(TestCase): def setUp(self): self.t = Task() # Themes are a special case that cannot be set via "task config" - with open(self.t.taskrc, 'a') as fh: - fh.write("include " + REPO_DIR + "/doc/rc/no-color.theme\n") + with open(self.t.taskrc, "a") as fh: + fh.write("include " + REPO_DIR + "/../doc/rc/no-color.theme\n") self.t.config("color.alternate", "") self.t.config("_forcecolor", "1") @@ -49,8 +49,8 @@ class TestBug1379(TestCase): self.t.config("color.label.sort", "") # For use with regex - self.RED = "\033\[31m" - self.CLEAR = "\033\[0m" + self.RED = "\033\\[31m" + self.CLEAR = "\033\\[0m" def test_color_BLOCKED(self): """color.tag.BLOCKED changes color of BLOCKED tasks""" @@ -158,8 +158,10 @@ class TestBug1379(TestCase): code, out, err = self.t("all +DELETED") self.assertRegex(out, self.RED + r".*Delete.*" + self.CLEAR) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-1637.t b/test/tw-1637.test.sh similarity index 100% rename from test/tw-1637.t rename to test/tw-1637.test.sh diff --git a/test/tw-1643.t b/test/tw-1643.test.sh similarity index 100% rename from test/tw-1643.t rename to test/tw-1643.test.sh diff --git a/test/tw-1688.t b/test/tw-1688.test.sh similarity index 100% rename from test/tw-1688.t rename to test/tw-1688.test.sh diff --git a/test/tw-1715.t b/test/tw-1715.test.sh similarity index 100% rename from test/tw-1715.t rename to test/tw-1715.test.sh diff --git a/test/tw-1718.t b/test/tw-1718.test.sh similarity index 100% rename from test/tw-1718.t rename to test/tw-1718.test.sh diff --git a/test/tw-1804.t b/test/tw-1804.test.sh similarity index 100% rename from test/tw-1804.t rename to test/tw-1804.test.sh diff --git a/test/tw-1837.t b/test/tw-1837.test.py old mode 100644 new mode 100755 similarity index 99% rename from test/tw-1837.t rename to test/tw-1837.test.py index cd51f4d8b..78ecbc012 --- a/test/tw-1837.t +++ b/test/tw-1837.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -51,6 +51,7 @@ from basetest import Task, TestCase # self.assertNotRegex(t, r) # self.tap("") + class TestBug1837(TestCase): def setUp(self): self.t = Task() @@ -62,8 +63,10 @@ class TestBug1837(TestCase): self.tap(out) self.tap(err) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-1883.t b/test/tw-1883.test.sh similarity index 100% rename from test/tw-1883.t rename to test/tw-1883.test.sh diff --git a/test/tw-1895.t b/test/tw-1895.test.sh similarity index 100% rename from test/tw-1895.t rename to test/tw-1895.test.sh diff --git a/test/tw-1938.t b/test/tw-1938.test.sh similarity index 100% rename from test/tw-1938.t rename to test/tw-1938.test.sh diff --git a/test/tw-1999.test.py b/test/tw-1999.test.py new file mode 100755 index 000000000..47bee92d8 --- /dev/null +++ b/test/tw-1999.test.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +############################################################################### +# +# Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import unittest + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestBug1999(TestCase): + """Bug 1999: Taskwarrior reports wrong active time""" + + def setUp(self): + self.t = Task() + + def test_correct_active_time(self): + """Ensure correct active time locally""" + desc = "Testing task" + self.t(("add", desc)) + self.t(("start", "1")) + self.t.faketime("+10m") + self.t(("stop", "1")) + + code, out, err = self.t(("info", "1")) + self.assertRegex(out, "duration: 0:10:0[0-5]") + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-20.t b/test/tw-20.test.py similarity index 92% rename from test/tw-20.t rename to test/tw-20.test.py index 4c0556759..a2daa97c1 100755 --- a/test/tw-20.t +++ b/test/tw-20.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -49,7 +49,7 @@ class TestBug20(TestCase): self.t.env["VISUAL"] = self.editor def test_annotate_edit_does_not_delete(self): - """ edit annotation should not delete then add untouched annotations """ + """edit annotation should not delete then add untouched annotations""" self.t("add tw-20") self.t("1 annotate 1st annotation") @@ -63,8 +63,8 @@ class TestBug20(TestCase): code, _timestamp1b, err = self.t("_get 1.annotations.1.entry") code, _timestamp2b, err = self.t("_get 1.annotations.2.entry") - self.assertEqual( _timestamp1a, _timestamp1b ) - self.assertEqual( _timestamp2a, _timestamp2b ) + self.assertEqual(_timestamp1a, _timestamp1b) + self.assertEqual(_timestamp2a, _timestamp2b) code, out, err = self.t("info") @@ -74,6 +74,7 @@ class TestBug20(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-2124.t b/test/tw-2124.test.sh similarity index 65% rename from test/tw-2124.t rename to test/tw-2124.test.sh index 2c75a63a1..34ccf1cfc 100755 --- a/test/tw-2124.t +++ b/test/tw-2124.test.sh @@ -7,9 +7,3 @@ # Filtering for description with a dash works task add foo-bar task foo-bar list | grep foo-bar - -# Filtering for tag with dash does not work right now -export EXPFAIL=true - -task add test +one-two -task +one-two list diff --git a/test/tw-2189.t b/test/tw-2189.test.sh similarity index 100% rename from test/tw-2189.t rename to test/tw-2189.test.sh diff --git a/test/tw-2257.t b/test/tw-2257.test.sh similarity index 100% rename from test/tw-2257.t rename to test/tw-2257.test.sh diff --git a/test/tw-2386.t b/test/tw-2386.test.sh similarity index 100% rename from test/tw-2386.t rename to test/tw-2386.test.sh diff --git a/test/tw-2392.t b/test/tw-2392.test.sh similarity index 100% rename from test/tw-2392.t rename to test/tw-2392.test.sh diff --git a/test/tw-2429.t b/test/tw-2429.test.sh similarity index 100% rename from test/tw-2429.t rename to test/tw-2429.test.sh diff --git a/test/tw-2451.t b/test/tw-2451.test.sh similarity index 100% rename from test/tw-2451.t rename to test/tw-2451.test.sh diff --git a/test/tw-2514.t b/test/tw-2514.test.sh similarity index 83% rename from test/tw-2514.t rename to test/tw-2514.test.sh index 47a93fa20..4033bde81 100755 --- a/test/tw-2514.t +++ b/test/tw-2514.test.sh @@ -6,9 +6,6 @@ task add Something I did yesterday task 1 mod start:yesterday+18h task 1 done end:yesterday+20h -# this does not work without journal.info -export EXPFAIL=true - # Check that 2 hour interval is reported by task info task info | grep -F "Start deleted" [[ ! -z `task info | grep -F "Start deleted (duration: 2:00:00)."` ]] diff --git a/test/tw-2530.t b/test/tw-2530.test.sh similarity index 100% rename from test/tw-2530.t rename to test/tw-2530.test.sh diff --git a/test/tw-2550.t b/test/tw-2550.test.sh similarity index 100% rename from test/tw-2550.t rename to test/tw-2550.test.sh diff --git a/test/tw-2575.t b/test/tw-2575.t deleted file mode 100644 index 246cf45d7..000000000 --- a/test/tw-2575.t +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -import sys -import os -import unittest -import re -import json - -# Ensure python finds the local simpletap module -sys.path.append(os.path.dirname(os.path.abspath(__file__))) - -from basetest import Task, TestCase - - -class TestExport(TestCase): - def setUp(self): - self.t = Task() - - self.t("add one") - self.t("add two project:strange") - self.t("add task1 +home project:A") - self.t("add task2 +work project:A") - - self.t.config("report.foo.description", "DESC") - self.t.config("report.foo.labels", "ID,DESCRIPTION") - self.t.config("report.foo.columns", "id,description") - self.t.config("report.foo.sort", "urgency+") - self.t.config("report.foo.filter", "project:A") - self.t.config("urgency.user.tag.home.coefficient", "15") - - def assertTaskEqual(self, t1, t2): - keys = [k for k in sorted(t1.keys()) if k not in ["entry", "modified", "uuid"]] - for k in keys: - self.assertEqual(t1[k], t2[k]) - - def test_exports(self): - """Verify exports work""" - code, out, err = self.t("export") - out = json.loads(out) - - self.assertEqual(len(out), 4) - - self.assertTaskEqual(out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0}) - self.assertTaskEqual(out[1], {"id": 2, "description": "two", "project": "strange", "status": "pending", "urgency": 1}) - self.assertTaskEqual(out[2], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8}) - self.assertTaskEqual(out[3], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8}) - - def test_exports_filter(self): - """Verify exports with filter work""" - code, out, err = self.t("one export") - out = json.loads(out) - - self.assertEqual(len(out), 1) - - self.assertTaskEqual(out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0}) - - def test_exports_with_limits_and_filter(self): - """Verify exports with limits and filter work""" - code, out, err = self.t("task limit:4 export") - out = json.loads(out) - - self.assertEqual(len(out), 2) - - self.assertTaskEqual(out[0], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8}) - self.assertTaskEqual(out[1], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8}) - - code, out, err = self.t("task limit:1 export") - out = json.loads(out) - - self.assertEqual(len(out), 1) - - self.assertTaskEqual(out[0], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8}) - - def test_exports_report(self): - """Verify exports with report work""" - code, out, err = self.t("export foo") - out = json.loads(out) - - self.assertEqual(len(out), 2) - - self.assertTaskEqual(out[0], {"id": 4, "description": "task2", "status": "pending", "project": "A", "tags": ["work"], "urgency": 1.8}) - self.assertTaskEqual(out[1], {"id": 3, "description": "task1", "status": "pending", "project": "A", "tags": ["home"], "urgency": 16.8}) - - -if __name__ == "__main__": - from simpletap import TAPTestRunner - - unittest.main(testRunner=TAPTestRunner()) - -# vim: ai sts=4 et sw=4 ft=python syntax=python diff --git a/test/tw-2575.test.py b/test/tw-2575.test.py new file mode 100755 index 000000000..0638fe49a --- /dev/null +++ b/test/tw-2575.test.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 + +import sys +import os +import unittest +import re +import json + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestExport(TestCase): + def setUp(self): + self.t = Task() + + self.t("add one") + self.t("add two project:strange") + self.t("add task1 +home project:A") + self.t("add task2 +work project:A") + + self.t.config("report.foo.description", "DESC") + self.t.config("report.foo.labels", "ID,DESCRIPTION") + self.t.config("report.foo.columns", "id,description") + self.t.config("report.foo.sort", "urgency+") + self.t.config("report.foo.filter", "project:A") + self.t.config("urgency.user.tag.home.coefficient", "15") + + def assertTaskEqual(self, t1, t2): + keys = [k for k in sorted(t1.keys()) if k not in ["entry", "modified", "uuid"]] + for k in keys: + self.assertEqual(t1[k], t2[k]) + + def test_exports(self): + """Verify exports work""" + code, out, err = self.t("export") + out = json.loads(out) + + self.assertEqual(len(out), 4) + + self.assertTaskEqual( + out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0} + ) + self.assertTaskEqual( + out[1], + { + "id": 2, + "description": "two", + "project": "strange", + "status": "pending", + "urgency": 1, + }, + ) + self.assertTaskEqual( + out[2], + { + "id": 3, + "description": "task1", + "status": "pending", + "project": "A", + "tags": ["home"], + "urgency": 16.8, + }, + ) + self.assertTaskEqual( + out[3], + { + "id": 4, + "description": "task2", + "status": "pending", + "project": "A", + "tags": ["work"], + "urgency": 1.8, + }, + ) + + def test_exports_filter(self): + """Verify exports with filter work""" + code, out, err = self.t("one export") + out = json.loads(out) + + self.assertEqual(len(out), 1) + + self.assertTaskEqual( + out[0], {"id": 1, "description": "one", "status": "pending", "urgency": 0} + ) + + def test_exports_with_limits_and_filter(self): + """Verify exports with limits and filter work""" + code, out, err = self.t("task limit:4 export") + out = json.loads(out) + + self.assertEqual(len(out), 2) + + self.assertTaskEqual( + out[0], + { + "id": 3, + "description": "task1", + "status": "pending", + "project": "A", + "tags": ["home"], + "urgency": 16.8, + }, + ) + self.assertTaskEqual( + out[1], + { + "id": 4, + "description": "task2", + "status": "pending", + "project": "A", + "tags": ["work"], + "urgency": 1.8, + }, + ) + + code, out, err = self.t("task limit:1 export") + out = json.loads(out) + + self.assertEqual(len(out), 1) + + self.assertTaskEqual( + out[0], + { + "id": 3, + "description": "task1", + "status": "pending", + "project": "A", + "tags": ["home"], + "urgency": 16.8, + }, + ) + + def test_exports_report(self): + """Verify exports with report work""" + code, out, err = self.t("export foo") + out = json.loads(out) + + self.assertEqual(len(out), 2) + + self.assertTaskEqual( + out[0], + { + "id": 4, + "description": "task2", + "status": "pending", + "project": "A", + "tags": ["work"], + "urgency": 1.8, + }, + ) + self.assertTaskEqual( + out[1], + { + "id": 3, + "description": "task1", + "status": "pending", + "project": "A", + "tags": ["home"], + "urgency": 16.8, + }, + ) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python syntax=python diff --git a/test/tw-2581.t b/test/tw-2581.test.sh similarity index 100% rename from test/tw-2581.t rename to test/tw-2581.test.sh diff --git a/test/tw-262.t b/test/tw-262.test.py similarity index 98% rename from test/tw-262.t rename to test/tw-262.test.py index 1ddff5179..74935075e 100755 --- a/test/tw-262.t +++ b/test/tw-262.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -78,8 +78,7 @@ class TestBug262(TestCase): self._check_expectation(" (project.isnt:tw)") def test_proj_isnt_parenthesis_space_leading_double(self): - """project.isnt works within parenthesis after a double leading space - """ + """project.isnt works within parenthesis after a double leading space""" self._check_expectation(" ( project.isnt:tw)") def test_proj_isnt_parenthesis_space_trailing(self): @@ -87,8 +86,7 @@ class TestBug262(TestCase): self._check_expectation("(project.isnt:tw) ") def test_proj_isnt_parenthesis_space_trailing_double(self): - """project.isnt works within parenthesis after a double trailing space - """ + """project.isnt works within parenthesis after a double trailing space""" self._check_expectation("(project.isnt:tw ) ") def test_proj_isnt_spaces_parenthesis(self): @@ -99,8 +97,10 @@ class TestBug262(TestCase): """project.isnt works within parenthesis and double spaces""" self._check_expectation(" ( project.isnt:tw ) ") + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-2689.t.cpp b/test/tw-2689.t.cpp deleted file mode 100644 index 3b67f1f4e..000000000 --- a/test/tw-2689.t.cpp +++ /dev/null @@ -1,89 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest test (12); - - // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); - - // Inform Task about the attributes in the JSON below - Task::attributes["depends"] = "string"; - Task::attributes["uuid"] = "string"; - - // depends in [..] string from a taskserver (issue#2689) - auto sample = "{" - "\"depends\":\"[\\\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\\\",\\\"e08e35fa-e42b-4de0-acc4-518fca8f6365\\\"]\"," - "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" - "}"; - auto json = Task (sample); - auto value = json.get ("uuid"); - test.is (value, "00000000-0000-0000-0000-000000000000", "json [..] uuid"); - value = json.get ("depends"); - test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json [..] depends"); - test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json [..] dep attr"); - test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json [..] dep attr"); - - // depends in comma-delimited string from a taskserver (deprecated format) - sample = "{" - "\"depends\":\"92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365\"," - "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" - "}"; - json = Task (sample); - value = json.get ("uuid"); - test.is (value, "00000000-0000-0000-0000-000000000000", "json comma-separated uuid"); - value = json.get ("depends"); - test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json comma-separated depends"); - test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json comma-separated dep attr"); - test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json comma-separated dep attr"); - - // depends in a JSON array from a taskserver - sample = "{" - "\"depends\":[\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\",\"e08e35fa-e42b-4de0-acc4-518fca8f6365\"]," - "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" - "}"; - json = Task (sample); - value = json.get ("uuid"); - test.is (value, "00000000-0000-0000-0000-000000000000", "json array uuid"); - value = json.get ("depends"); - test.is (value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", "json array depends"); - test.ok (json.has ("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json array dep attr"); - test.ok (json.has ("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json array dep attr"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/test/tw-295.t b/test/tw-295.test.py similarity index 91% rename from test/tw-295.t rename to test/tw-295.test.py index 15136b288..0cb9932e1 100755 --- a/test/tw-295.t +++ b/test/tw-295.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase @@ -40,7 +40,7 @@ class TestBug295(TestCase): def test_subst_with_slashes(self): """Test substitution containing slashes""" - self.t('add -- one/two/three') + self.t("add -- one/two/three") # Python string contains \\\\, converts to internal \\. # Something in the command processing converts \\ --> \. @@ -50,12 +50,14 @@ class TestBug295(TestCase): # code, out, err = self.t('rc.debug.parser=2 1 modify /\\\\/two\\\\//TWO/') # self.tap(err) - self.t('1 modify /\\\\/two\\\\//TWO/') - code, out, err = self.t('list') - self.assertIn('oneTWOthree', out) + self.t("1 modify /\\\\/two\\\\//TWO/") + code, out, err = self.t("list") + self.assertIn("oneTWOthree", out) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/tw-3102.t b/test/tw-3102.test.sh similarity index 100% rename from test/tw-3102.t rename to test/tw-3102.test.sh diff --git a/test/tw-3109.t b/test/tw-3109.test.sh similarity index 100% rename from test/tw-3109.t rename to test/tw-3109.test.sh diff --git a/test/tw-3527.test.py b/test/tw-3527.test.py new file mode 100755 index 000000000..94b81e7ff --- /dev/null +++ b/test/tw-3527.test.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +import sys +import os +import unittest +import re +import json +import string + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase + + +class TestExport(TestCase): + def setUp(self): + self.t = Task() + + # pretty arbitrary, just need several unique tasks + for letter in string.ascii_lowercase: + self.t(f"add test_task +{letter}") + self.t(f"+{letter} done") + + def test_export_stability_for_multiple_id_0(self): + exports = [self.t("export")[1] for _ in range(2)] + json_lists = [json.loads(s.strip()) for s in exports] + # to rule out a typo causing two failed exports + self.assertEqual(len(json_lists[0]), len(string.ascii_lowercase)) + # for better diff view + self.assertEqual(json_lists[0], json_lists[1]) + # the real test + self.assertEqual(exports[0], exports[1]) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) diff --git a/test/tw_2689_test.cpp b/test/tw_2689_test.cpp new file mode 100644 index 000000000..81610eeba --- /dev/null +++ b/test/tw_2689_test.cpp @@ -0,0 +1,96 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +// cmake.h include header must come first + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest test(12); + + // Ensure environment has no influence. + unsetenv("TASKDATA"); + unsetenv("TASKRC"); + + // Inform Task about the attributes in the JSON below + Task::attributes["depends"] = "string"; + Task::attributes["uuid"] = "string"; + + // depends in [..] string from a taskserver (issue#2689) + auto sample = + "{" + "\"depends\":\"[\\\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\\\",\\\"e08e35fa-e42b-4de0-acc4-" + "518fca8f6365\\\"]\"," + "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" + "}"; + auto json = Task(sample); + auto value = json.get("uuid"); + test.is(value, "00000000-0000-0000-0000-000000000000", "json [..] uuid"); + value = json.get("depends"); + test.is(value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", + "json [..] depends"); + test.ok(json.has("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json [..] dep attr"); + test.ok(json.has("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json [..] dep attr"); + + // depends in comma-delimited string from a taskserver (deprecated format) + sample = + "{" + "\"depends\":\"92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365\"," + "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" + "}"; + json = Task(sample); + value = json.get("uuid"); + test.is(value, "00000000-0000-0000-0000-000000000000", "json comma-separated uuid"); + value = json.get("depends"); + test.is(value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", + "json comma-separated depends"); + test.ok(json.has("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json comma-separated dep attr"); + test.ok(json.has("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json comma-separated dep attr"); + + // depends in a JSON array from a taskserver + sample = + "{" + "\"depends\":[\"92a40a34-37f3-4785-8ca1-ff89cfbfd105\",\"e08e35fa-e42b-4de0-acc4-" + "518fca8f6365\"]," + "\"uuid\":\"00000000-0000-0000-0000-000000000000\"" + "}"; + json = Task(sample); + value = json.get("uuid"); + test.is(value, "00000000-0000-0000-0000-000000000000", "json array uuid"); + value = json.get("depends"); + test.is(value, "92a40a34-37f3-4785-8ca1-ff89cfbfd105,e08e35fa-e42b-4de0-acc4-518fca8f6365", + "json array depends"); + test.ok(json.has("dep_92a40a34-37f3-4785-8ca1-ff89cfbfd105"), "json array dep attr"); + test.ok(json.has("dep_e08e35fa-e42b-4de0-acc4-518fca8f6365"), "json array dep attr"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/uda.t b/test/uda.test.py similarity index 89% rename from test/uda.t rename to test/uda.test.py index 498642294..1919e8600 100755 --- a/test/uda.t +++ b/test/uda.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import re import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -103,8 +103,8 @@ class TestUdaDate(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+[\d\/]+\s+with") - self.assertRegex(out, "2\s+without") + self.assertRegex(out, r"1\s+[\d\/]+\s+with") + self.assertRegex(out, r"2\s+without") def test_uda_bad_date_task(self): """Add tasks with an invalid UDA date""" @@ -134,7 +134,7 @@ class TestUdaDefault(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+strong\s+one") + self.assertRegex(out, r"1\s+strong\s+one") def test_uda_default_task(self): """Add tasks with default UDA""" @@ -143,7 +143,7 @@ class TestUdaDefault(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+weak\s+two") + self.assertRegex(out, r"1\s+weak\s+two") def test_uda_without_default_task(self): """Add tasks without default UDA""" @@ -152,7 +152,7 @@ class TestUdaDefault(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+weak\s+10\s+three") + self.assertRegex(out, r"1\s+weak\s+10\s+three") class TestUdaDuration(TestBaseUda): @@ -180,8 +180,7 @@ class TestUdaDuration(TestBaseUda): """Add tasks with an invalid UDA duration""" code, out, err = self.t.runError("add bad extra:bad_duration") self.assertNotIn("Created task", out) - self.assertIn("The duration value 'bad_duration' is not supported", - err) + self.assertIn("The duration value 'bad_duration' is not supported", err) class TestUdaNumeric(TestBaseUda): @@ -200,8 +199,8 @@ class TestUdaNumeric(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+\d+\s+with") - self.assertRegex(out, "2\s+without") + self.assertRegex(out, r"1\s+\d+\s+with") + self.assertRegex(out, r"2\s+without") def test_uda_bad_numeric_task(self): """Add tasks with an invalid UDA numeric""" @@ -225,8 +224,8 @@ class TestUdaString(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+one two\s+with") - self.assertRegex(out, "2\s+without") + self.assertRegex(out, r"1\s+one two\s+with") + self.assertRegex(out, r"2\s+without") class TestUdaValue(TestBaseUda): @@ -245,8 +244,8 @@ class TestUdaValue(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+weak\s+one") - self.assertRegex(out, "2\s+strong\s+two") + self.assertRegex(out, r"1\s+weak\s+one") + self.assertRegex(out, r"2\s+strong\s+two") def test_uda_invalid_value_task(self): """Add tasks with invalid UDA value""" @@ -255,12 +254,11 @@ class TestUdaValue(TestBaseUda): self.assertIn("Created task", out) code, out, err = self.t.runError("add two extra:toxic") - self.assertIn("The 'extra' attribute does not allow a value of " - "'toxic'", err) + self.assertIn("The 'extra' attribute does not allow a value of " "'toxic'", err) code, out, err = self.t("uda") - self.assertRegex(out, "1\s+strong\s+one") - self.assertNotRegex(out, "1\s+toxic\s+two") + self.assertRegex(out, r"1\s+strong\s+one") + self.assertNotRegex(out, r"1\s+toxic\s+two") class TestBug1063(TestCase): @@ -300,8 +298,8 @@ class TestBug21(TestCase): def test_almost_UDA(self): """21: do not match a UDA if not followed by colon""" - self.t.config('uda.foo.type', 'string') - self.t.config('uda.foo.label', 'FOO') + self.t.config("uda.foo.type", "string") + self.t.config("uda.foo.label", "FOO") self.t("add this is a foo bar") code, out, err = self.t("1 info") @@ -315,12 +313,12 @@ class Test1447(TestCase): def test_filter_uda(self): """1447: Verify ability to filter on empty UDA that resembles named date""" - self.t.config('uda.sep.type', 'string') - self.t('add one') - self.t('add two sep:foo') - code, out, err = self.t('sep: list') + self.t.config("uda.sep.type", "string") + self.t("add one") + self.t("add two sep:foo") + code, out, err = self.t("sep: list") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('one', out) + self.assertIn("one", out) class Test1542(TestCase): @@ -334,18 +332,18 @@ class Test1542(TestCase): 1542: Make sure the numeric UDA value 1187962 does not get converted to scientific notation on export. """ - self.t('add large bugid:1187962') - code, out, err = self.t('1 export') - self.assertIn("\"bugid\":1187962,", out) + self.t("add large bugid:1187962") + code, out, err = self.t("1 export") + self.assertIn('"bugid":1187962,', out) def test_small_numeric_uda_retains_value(self): """ 1542: Make sure the numeric UDA value 43.21 does not get converted to integer on export. """ - self.t('add small bugid:43.21') - code, out, err = self.t('1 export') - self.assertIn("\"bugid\":43.21", out) + self.t("add small bugid:43.21") + code, out, err = self.t("1 export") + self.assertIn('"bugid":43.21', out) class TestBug1622(TestCase): @@ -364,6 +362,7 @@ class TestBug1622(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/uda_orphan.t b/test/uda_orphan.test.py similarity index 93% rename from test/uda_orphan.t rename to test/uda_orphan.test.py index 99984ca35..0ba3e9c03 100755 --- a/test/uda_orphan.t +++ b/test/uda_orphan.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -48,7 +48,9 @@ class TestUDAOrphans(TestCase): """Verify that orphans are preserved during various operations""" self.t("rc.uda.extra.type:string rc.uda.extra.label:Extra add one extra:foo") - code, out, err = self.t("rc.uda.extra.type:string rc.uda.extra.label:Extra _get 1.extra") + code, out, err = self.t( + "rc.uda.extra.type:string rc.uda.extra.label:Extra _get 1.extra" + ) self.assertEqual("foo\n", out) # DOM access for orphans is not supported. @@ -56,12 +58,12 @@ class TestUDAOrphans(TestCase): # 'info' should show orphans. code, out, err = self.t("1 info") - self.assertRegex(out, "\[extra\s+foo\]") + self.assertRegex(out, r"\[extra\s+foo\]") # 'modify' should not change the orphan self.t("1 modify /one/two/") code, out, err = self.t("1 info") - self.assertRegex(out, "\[extra\s+foo\]") + self.assertRegex(out, r"\[extra\s+foo\]") # 'export' should include orphans. code, out, err = self.t("1 export") @@ -98,11 +100,12 @@ class TestUDAOrphans(TestCase): # Only the first task should be identified as orphan code, out, err = self.t("udas") - self.assertRegex(out, r'extra\s+1\s+1 Orphan UDA') + self.assertRegex(out, r"extra\s+1\s+1 Orphan UDA") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/uda_report.t b/test/uda_report.test.py similarity index 99% rename from test/uda_report.t rename to test/uda_report.test.py index 0b54ae15c..b4853c478 100755 --- a/test/uda_report.t +++ b/test/uda_report.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -70,6 +70,7 @@ class TestUdaReports(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/uda_sort.t b/test/uda_sort.test.py similarity index 56% rename from test/uda_sort.t rename to test/uda_sort.test.py index 92ae964d6..29116d5e4 100755 --- a/test/uda_sort.t +++ b/test/uda_sort.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -39,89 +39,89 @@ class TestUDACustomSort(TestCase): @classmethod def setUpClass(cls): cls.t = Task() - cls.t.config('uda.foo.type', 'string') - cls.t.config('uda.foo.label', 'Foo') - cls.t.config('uda.foo.values', 'H,M,L,') - cls.t.config('report.list.columns', 'id,description,foo') - cls.t.config('report.list.labels', 'ID,Desc,Foo') - cls.t('add four foo:H') - cls.t('add three foo:M') - cls.t('add two foo:L') - cls.t('add one') + cls.t.config("uda.foo.type", "string") + cls.t.config("uda.foo.label", "Foo") + cls.t.config("uda.foo.values", "H,M,L,") + cls.t.config("report.list.columns", "id,description,foo") + cls.t.config("report.list.labels", "ID,Desc,Foo") + cls.t("add four foo:H") + cls.t("add three foo:M") + cls.t("add two foo:L") + cls.t("add one") def test_ascending(self): """Ascending custom sort order""" - self.t.config('uda.foo.values', 'H,M,L,') - code, out, err = self.t('rc.report.list.sort:foo+ list') + self.t.config("uda.foo.values", "H,M,L,") + code, out, err = self.t("rc.report.list.sort:foo+ list") - one = out.find('one') - two = out.find('two') - three = out.find('three') - four = out.find('four') + one = out.find("one") + two = out.find("two") + three = out.find("three") + four = out.find("four") - self.assertTrue(one < two) - self.assertTrue(two < three) + self.assertTrue(one < two) + self.assertTrue(two < three) self.assertTrue(three < four) def test_descending(self): """Descending custom sort order""" - self.t.config('uda.foo.values', 'H,M,L,') - code, out, err = self.t('rc.report.list.sort:foo- list') + self.t.config("uda.foo.values", "H,M,L,") + code, out, err = self.t("rc.report.list.sort:foo- list") - one = out.find('one') - two = out.find('two') - three = out.find('three') - four = out.find('four') + one = out.find("one") + two = out.find("two") + three = out.find("three") + four = out.find("four") - self.assertTrue(four < three) + self.assertTrue(four < three) self.assertTrue(three < two) - self.assertTrue(two < one) + self.assertTrue(two < one) def test_ridiculous(self): """Ridiculous custom sort order""" - self.t.config('uda.foo.values', 'H,M,,L') - code, out, err = self.t('rc.report.list.sort:foo- list') + self.t.config("uda.foo.values", "H,M,,L") + code, out, err = self.t("rc.report.list.sort:foo- list") - one = out.find('one') - two = out.find('two') - three = out.find('three') - four = out.find('four') + one = out.find("one") + two = out.find("two") + three = out.find("three") + four = out.find("four") - self.assertTrue(four < three) + self.assertTrue(four < three) self.assertTrue(three < one) - self.assertTrue(one < two) + self.assertTrue(one < two) class TestUDADefaultSort(TestCase): @classmethod def setUpClass(cls): cls.t = Task() - cls.t.config('uda.foo.type', 'string') - cls.t.config('uda.foo.label', 'Foo') - cls.t.config('report.list.columns', 'id,description,foo') - cls.t.config('report.list.labels', 'ID,Desc,Foo') - cls.t('add one foo:A') - cls.t('add three') - cls.t('add two foo:B') + cls.t.config("uda.foo.type", "string") + cls.t.config("uda.foo.label", "Foo") + cls.t.config("report.list.columns", "id,description,foo") + cls.t.config("report.list.labels", "ID,Desc,Foo") + cls.t("add one foo:A") + cls.t("add three") + cls.t("add two foo:B") def test_ascending(self): """Ascending default sort order""" - code, out, err = self.t('rc.report.list.sort:foo+ list') + code, out, err = self.t("rc.report.list.sort:foo+ list") - one = out.find('one') - two = out.find('two') - three = out.find('three') + one = out.find("one") + two = out.find("two") + three = out.find("three") self.assertTrue(one < two) self.assertTrue(two < three) def test_descending(self): """Descending default sort order""" - code, out, err = self.t('rc.report.list.sort:foo- list') + code, out, err = self.t("rc.report.list.sort:foo- list") - one = out.find('one') - two = out.find('two') - three = out.find('three') + one = out.find("one") + two = out.find("two") + three = out.find("three") self.assertTrue(one < three) self.assertTrue(two < one) @@ -134,12 +134,12 @@ class TestBug1319(TestCase): def test_uda_sorting(self): """1319: Verify that UDAs are sorted according to defined order""" - self.t.config("uda.when.type", "string") - self.t.config("uda.when.values", "night,evening,noon,morning") + self.t.config("uda.when.type", "string") + self.t.config("uda.when.values", "night,evening,noon,morning") self.t.config("report.foo.columns", "id,when,description") - self.t.config("report.foo.labels", "ID,WHEN,DESCRIPTION") - self.t.config("report.foo.sort", "when+") + self.t.config("report.foo.labels", "ID,WHEN,DESCRIPTION") + self.t.config("report.foo.sort", "when+") self.t("add one when:night") self.t("add two when:evening") @@ -147,11 +147,15 @@ class TestBug1319(TestCase): self.t("add four when:morning") code, out, err = self.t("rc.verbose:nothing foo") - self.assertRegex(out, "4\s+morning\s+four\s+3\s+noon\s+three\s+2\s+evening\s+two\s+1\s+night\s+one") + self.assertRegex( + out, + r"4\s+morning\s+four\s+3\s+noon\s+three\s+2\s+evening\s+two\s+1\s+night\s+one", + ) if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/undo.t b/test/undo.test.py similarity index 59% rename from test/undo.t rename to test/undo.test.py index 55fedb789..5df49155c 100755 --- a/test/undo.t +++ b/test/undo.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -28,7 +27,9 @@ import sys import os +import re import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -42,24 +43,43 @@ class TestUndo(TestCase): def test_add_undo(self): """'add' then 'undo'""" - self.t('add one') - code, out, err = self.t('_get 1.status') - self.assertEqual(out.strip(), 'pending') - self.t('undo', input="y\n") - code, out, err = self.t('_get 1.status') - self.assertEqual(out.strip(), '') + self.t("add one") + code, out, err = self.t("_get 1.status") + self.assertEqual(out.strip(), "pending") + self.t("undo", input="y\n") + code, out, err = self.t("_get 1.status") + self.assertEqual(out.strip(), "") def test_add_done_undo(self): """'add' then 'done' then 'undo'""" - self.t('add two') - code, out, err = self.t('_get 1.status') - self.assertEqual(out.strip(), 'pending') - self.t('1 done') - code, out, err = self.t('_get 1.status') - self.assertEqual(out.strip(), 'completed') - self.t('undo', input="y\n") - code, out, err = self.t('_get 1.status') - self.assertEqual(out.strip(), 'pending') + self.t("add two") + code, out, err = self.t("_get 1.status") + self.assertEqual(out.strip(), "pending") + self.t("1 done") + code, out, err = self.t("_get 1.status") + self.assertEqual(out.strip(), "completed") + self.t("undo", input="y\n") + code, out, err = self.t("_get 1.status") + self.assertEqual(out.strip(), "pending") + + def test_modify_multiple_tasks(self): + """'add' then 'done' then 'undo'""" + self.t("add one") + self.t("add two") + self.t("add three") + self.t("rc.bulk=0 1,2,3 modify +sometag") + code, out, err = self.t("undo", input="y\n") + # This undo output should show one tag modification for each task, possibly with some + # modification-time updates if the modifications spanned a second boundary. + self.assertRegex( + out, + "\s+".join( + [ + r"""[0-9a-f-]{36} (Update property 'modified' from\s+'[0-9]+' to '[0-9]+'\s+)?Add tag 'sometag'\s+Add property 'tags' with value 'sometag'""" + ] + * 3 + ), + ) def test_undo_en_passant(self): """Verify that en-passant changes during undo are an error""" @@ -74,33 +94,13 @@ class TestUndoStyle(TestCase): self.t("add one project:foo priority:H") self.t("1 modify +tag project:bar priority:") - @unittest.expectedFailure # undo diffs are not supported - def test_undo_side_style(self): - """Test that 'rc.undo.style:side' generates the right output""" - self.t.config("undo.style", "side") + def test_undo_output(self): + """Test that 'task undo' generates the right output""" code, out, err = self.t("undo", input="n\n") - self.assertNotRegex(out, "-tags:\s*\n\+tags:\s+tag") - self.assertRegex(out, "tags\s+tag\s*") - - @unittest.expectedFailure # undo diffs are not supported - def test_undo_diff_style(self): - """Test that 'rc.undo.style:diff' generates the right output""" - self.t.config("undo.style", "diff") - code, out, err = self.t("undo", input="n\n") - self.assertRegex(out, "-tags:\s*\n\+tags:\s+tag") - self.assertNotRegex(out, "tags\s+tag\s*") - - def test_undo_diff_operations(self): - code, out, err = self.t("undo", input="n\n") - - # If the clock ticks a second between `add` and `modify` there is a - # fifth operation setting the `modified` property. - self.assertRegex(out, "The following [4|5] operations would be reverted:") - - self.assertIn("tag_tag: -> x", out) - self.assertIn("tags: -> tag", out) - self.assertIn("project: foo -> bar", out) - self.assertIn("priority: H -> ", out) + self.assertNotRegex(out, "-tags:\\s*\n\\+tags:\\s+tag") + self.assertRegex(out, r"Delete property 'priority'") + self.assertRegex(out, r"Update property 'project'") + self.assertRegex(out, r"Add tag 'tag'") class TestBug634(TestCase): @@ -115,11 +115,12 @@ class TestBug634(TestCase): # If a prompt happens, the test will timeout on input (exitcode != 0) code, out, err = self.t("rc.confirmation=off undo") code, out, err = self.t("_get 1.description") - self.assertEqual(out.strip(), '') # task is gone + self.assertEqual(out.strip(), "") # task is gone if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/unicode.t b/test/unicode.test.py similarity index 86% rename from test/unicode.t rename to test/unicode.test.py index 2850d9565..8c7491870 100755 --- a/test/unicode.t +++ b/test/unicode.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -47,13 +47,19 @@ class TestUnicode(TestCase): self.t("add ¥£€¢₡₢₣₤₥₦₧₨₩₪₫₭₮₯ ") self.t("add Pchnąć w tę łódź jeża lub ośm skrzyń fig") self.t("add ๏ เป็นมนุษย์สุดประเสริฐเลิศคุณค่า") - self.t("add イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラムイ ロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム") + self.t( + "add イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラムイ ロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム" + ) self.t("add いろはにほへとちりぬるを") - self.t("add D\\'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh") + self.t( + "add D\\'fhuascail Íosa, Úrmhac na hÓighe Beannaithe, pór Éava agus Ádhaimh" + ) self.t("add Árvíztűrő tükörfúrógép") self.t("add Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa") self.t("add Sævör grét áðan því úlpan var ónýt") - self.t("add Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon.") + self.t( + "add Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Wolther spillede på xylofon." + ) self.t("add Falsches Üben von Xylophonmusik quält jeden größeren Zwerg") self.t("add Zwölf Boxkämpfer jagten Eva quer über den Sylter Deich") self.t("add Heizölrückstoßabdämpfung") @@ -72,15 +78,15 @@ class TestUnicode(TestCase): def test_utf8_tag(self): """Verify that UTF8 can be used in a tag""" - self.t("add utf8 in tag +Zwölf"); + self.t("add utf8 in tag +Zwölf") code, out, err = self.t("_get 1.tags") - self.assertEqual("Zwölf\n", out); + self.assertEqual("Zwölf\n", out) def test_unicode_escape1(self): """Verify U+NNNN unicode sequences""" self.t("add Price U+20AC4") code, out, err = self.t("_get 1.description") - self.assertEqual("Price €4\n", out); + self.assertEqual("Price €4\n", out) def test_unicode_escape2(self): """Verify \\uNNNN unicode sequences""" @@ -93,10 +99,12 @@ class TestUnicode(TestCase): # $ echo add 'Price \u20A43' # \ is preserved self.t(r"add 'Price \u20A43'") code, out, err = self.t("_get 1.description") - self.assertEqual("Price ₤3\n", out); + self.assertEqual("Price ₤3\n", out) + if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/unique.t b/test/unique.test.py similarity index 93% rename from test/unique.t rename to test/unique.test.py index 7dcc706bd..2204b0e9a 100755 --- a/test/unique.t +++ b/test/unique.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -68,18 +68,18 @@ class TestUnique(TestCase): def test_unique_status(self): """Verify that unique status values are correctly counted""" code, out, err = self.t("_unique status") - self.assertIn("pending", out) + self.assertIn("pending", out) self.assertIn("completed", out) - self.assertIn("deleted", out) + self.assertIn("deleted", out) def test_unique_description(self): """Verify that unique description values are correctly counted""" code, out, err = self.t("_unique description") - self.assertIn("one", out) - self.assertIn("two", out) + self.assertIn("one", out) + self.assertIn("two", out) self.assertIn("three", out) - self.assertIn("four", out) - self.assertIn("five", out) + self.assertIn("four", out) + self.assertIn("five", out) def test_unique_id(self): """Verify that unique id values are correctly counted""" @@ -93,6 +93,7 @@ class TestUnique(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/unusual_task.test.py b/test/unusual_task.test.py new file mode 100755 index 000000000..18f8efffe --- /dev/null +++ b/test/unusual_task.test.py @@ -0,0 +1,204 @@ +#!/usr/bin/env python3 +############################################################################### +# +# Copyright 2025 Dustin J. Mitchell +# +# 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. +# +# https://www.opensource.org/licenses/mit-license.php +# +############################################################################### + +import sys +import os +import re +import time +import json +import unittest + +# Ensure python finds the local simpletap module +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from basetest import Task, TestCase +from basetest.utils import run_cmd_wait, CMAKE_BINARY_DIR + + +class TestUnusualTasks(TestCase): + def setUp(self): + """Executed before each test in the class""" + self.t = Task() + self.t.config( + "report.custom-report.columns", + "id,description,entry,start,end,due,scheduled,modified,until", + ) + self.t.config("verbose", "nothing") + + def make_task(self, **props): + make_tc_task = os.path.abspath( + os.path.join(CMAKE_BINARY_DIR, "test", "make_tc_task") + ) + cmd = [make_tc_task, self.t.datadir] + for p, v in props.items(): + cmd.append(f"{p}={v}") + _, out, _ = run_cmd_wait(cmd) + return out.strip() + + def test_empty_task_info(self): + uuid = self.make_task() + _, out, _ = self.t(f"{uuid} info") + self.assertNotIn("Entered", out) + self.assertNotIn("Waiting", out) + self.assertNotIn("Last modified", out) + self.assertNotIn("Start", out) + self.assertNotIn("End", out) + self.assertNotIn("Due", out) + self.assertNotIn("Until", out) + self.assertRegex(out, r"Status\s+Pending") + + def test_modify_empty_task(self): + uuid = self.make_task() + self.t(f"{uuid} modify a description +taggy due:tomorrow") + _, out, _ = self.t(f"{uuid} info") + self.assertRegex(out, r"Description\s+a description") + self.assertRegex(out, r"Tags\s+taggy") + + def test_empty_task_recurring(self): + uuid = self.make_task(status="recurring") + _, out, _ = self.t(f"{uuid} info") + self.assertRegex(out, r"Status\s+Recurring") + _, out, _ = self.t(f"{uuid} custom-report") + + def test_recurring_invalid_rtype(self): + uuid = self.make_task( + status="recurring", due=str(int(time.time())), rtype="occasional" + ) + _, out, _ = self.t(f"{uuid} info") + self.assertRegex(out, r"Status\s+Recurring") + self.assertRegex(out, r"Recurrence type\s+occasional") + _, out, _ = self.t(f"{uuid} custom-report") + + def test_recurring_invalid_recur(self): + uuid = self.make_task( + status="recurring", + due=str(int(time.time())), + rtype="periodic", + recur="xxxxx", + ) + _, out, _ = self.t(f"{uuid} info") + self.assertRegex(out, r"Status\s+Recurring") + self.assertRegex(out, r"Recurrence type\s+periodic") + _, out, _ = self.t(f"{uuid} custom-report") + + def test_recurring_bad_quarters_rtype(self): + uuid = self.make_task( + status="recurring", due=str(int(time.time())), rtype="periodic", recur="9aq" + ) + _, out, _ = self.t(f"{uuid} custom-report") + + def test_invalid_entry_info(self): + uuid = self.make_task(entry="abcdef") + _, out, _ = self.t(f"{uuid} info") + self.assertNotIn("Entered", out) + + def test_invalid_modified_info(self): + uuid = self.make_task(modified="abcdef") + _, out, _ = self.t(f"{uuid} info") + self.assertNotIn(r"Last modified", out) + + def test_invalid_start_info(self): + uuid = self.make_task(start="abcdef") + _, out, _ = self.t(f"{uuid} info") + + def test_invalid_dates_report(self): + uuid = self.make_task( + wait="wait", + scheduled="scheduled", + start="start", + due="due", + end="end", + until="until", + modified="modified", + ) + _, out, _ = self.t(f"{uuid} custom-report") + + def test_invalid_dates_stop(self): + uuid = self.make_task( + wait="wait", + scheduled="scheduled", + start="start", + due="due", + end="end", + until="until", + modified="modified", + ) + _, out, _ = self.t(f"{uuid} stop") + + def test_invalid_dates_modify(self): + uuid = self.make_task( + wait="wait", + scheduled="scheduled", + start="start", + due="due", + end="end", + until="until", + modified="modified", + ) + _, out, _ = self.t(f"{uuid} mod a description +tag") + + def test_invalid_dates_info(self): + uuid = self.make_task( + wait="wait", + scheduled="scheduled", + start="start", + due="due", + end="end", + until="until", + modified="modified", + ) + _, out, _ = self.t(f"{uuid} info") + self.assertNotRegex("^Entered\s+", out) + self.assertNotRegex("^Start\s+", out) + self.assertIn(r"Wait set to 'wait'", out) + self.assertIn(r"Scheduled set to 'scheduled'", out) + self.assertIn(r"Start set to 'start'", out) + self.assertIn(r"Due set to 'due'", out) + self.assertIn(r"End set to 'end'", out) + self.assertIn(r"Until set to 'until'", out) + # (note that 'modified' is not shown in the journal) + + def test_invalid_dates_export(self): + uuid = self.make_task( + wait="wait", + scheduled="scheduled", + start="start", + due="due", + end="end", + until="until", + modified="modified", + ) + _, out, _ = self.t(f"{uuid} export") + json.loads(out) + + +if __name__ == "__main__": + from simpletap import TAPTestRunner + + unittest.main(testRunner=TAPTestRunner()) + +# vim: ai sts=4 et sw=4 ft=python diff --git a/test/upgrade.t b/test/upgrade.test.py similarity index 88% rename from test/upgrade.t rename to test/upgrade.test.py index 1fdc52813..d022cc7ee 100755 --- a/test/upgrade.t +++ b/test/upgrade.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -44,23 +44,24 @@ class TestUpgrade(TestCase): self.t("add one") code, out, err = self.t("1 info") - self.assertRegex(out, "Status\s+Pending") + self.assertRegex(out, r"Status\s+Pending") self.t("1 modify due:tomorrow recur:weekly") self.t("list") code, out, err = self.t("1 info") - self.assertRegex(out, "Status\s+Recurring") - self.assertRegex(out, "Recurrence\s+weekly") + self.assertRegex(out, r"Status\s+Recurring") + self.assertRegex(out, r"Recurrence\s+weekly") # Also check for the presence of a children task with pending state code, out, err = self.t("2 info") - self.assertRegex(out, "Status\s+Pending") - self.assertRegex(out, "Recurrence\s+weekly") + self.assertRegex(out, r"Status\s+Pending") + self.assertRegex(out, r"Recurrence\s+weekly") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/urgency.t b/test/urgency.test.py similarity index 70% rename from test/urgency.t rename to test/urgency.test.py index 75c23707d..489dd786b 100755 --- a/test/urgency.t +++ b/test/urgency.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -40,94 +40,94 @@ class TestUrgency(TestCase): def setUpClass(cls): """Executed once before any test in the class""" cls.t = Task() - cls.t.config("urgency.uda.priority.H.coefficient", "10") - cls.t.config("urgency.uda.priority.M.coefficient", "6.5") - cls.t.config("urgency.uda.priority.L.coefficient", "3") - cls.t.config("urgency.active.coefficient", "10") - cls.t.config("urgency.project.coefficient", "10") - cls.t.config("urgency.due.coefficient", "10") - cls.t.config("urgency.blocking.coefficient", "10") - cls.t.config("urgency.blocked.coefficient", "10") - cls.t.config("urgency.annotations.coefficient", "10") - cls.t.config("urgency.tags.coefficient", "10") - cls.t.config("urgency.waiting.coefficient", "-10") - cls.t.config("urgency.user.tag.next.coefficient", "10") + cls.t.config("urgency.uda.priority.H.coefficient", "10") + cls.t.config("urgency.uda.priority.M.coefficient", "6.5") + cls.t.config("urgency.uda.priority.L.coefficient", "3") + cls.t.config("urgency.active.coefficient", "10") + cls.t.config("urgency.project.coefficient", "10") + cls.t.config("urgency.due.coefficient", "10") + cls.t.config("urgency.blocking.coefficient", "10") + cls.t.config("urgency.blocked.coefficient", "10") + cls.t.config("urgency.annotations.coefficient", "10") + cls.t.config("urgency.tags.coefficient", "10") + cls.t.config("urgency.waiting.coefficient", "-10") + cls.t.config("urgency.user.tag.next.coefficient", "10") cls.t.config("urgency.user.project.PROJECT.coefficient", "10") - cls.t.config("urgency.user.tag.TAG.coefficient", "10") - cls.t.config("confirmation", "0") + cls.t.config("urgency.user.tag.TAG.coefficient", "10") + cls.t.config("confirmation", "0") - cls.t("add control") # 1 + cls.t("add control") # 1 - cls.t("add 1a pri:H") # 2 - cls.t("add 1b pri:M") # 3 - cls.t("add 1c pri:L") # 4 + cls.t("add 1a pri:H") # 2 + cls.t("add 1b pri:M") # 3 + cls.t("add 1c pri:L") # 4 - cls.t("add 2a project:P") # 5 + cls.t("add 2a project:P") # 5 - cls.t("add 3a") # 6 + cls.t("add 3a") # 6 cls.t("6 start") - cls.t("add 4a +next") # 7 + cls.t("add 4a +next") # 7 - cls.t("add 5a +one") # 8 - cls.t("add 5b +one +two") # 9 - cls.t("add 5c +one +two +three") # 10 - cls.t("add 5d +one +two +three +four") # 11 + cls.t("add 5a +one") # 8 + cls.t("add 5b +one +two") # 9 + cls.t("add 5c +one +two +three") # 10 + cls.t("add 5d +one +two +three +four") # 11 - cls.t("add 6a") # 12 + cls.t("add 6a") # 12 cls.t("12 annotate A") - cls.t("add 6b") # 13 + cls.t("add 6b") # 13 cls.t("13 annotate A") cls.t("13 annotate B") - cls.t("add 6c") # 14 + cls.t("add 6c") # 14 cls.t("14 annotate A") cls.t("14 annotate B") cls.t("14 annotate C") - cls.t("add 6d") # 15 + cls.t("add 6d") # 15 cls.t("15 annotate A") cls.t("15 annotate B") cls.t("15 annotate C") cls.t("15 annotate D") - cls.t("add 7a wait:10y") # 16 + cls.t("add 7a wait:10y") # 16 - cls.t("add 8a") # 17 - cls.t("add 8b depends:17") # 18 + cls.t("add 8a") # 17 + cls.t("add 8b depends:17") # 18 - cls.t("add 9a due:-10d") # 19 - cls.t("add 9b due:-7d") # 20 - cls.t("add 9c due:-6d") # 21 - cls.t("add 9d due:-5d") # 22 - cls.t("add 9e due:-4d") # 23 - cls.t("add 9f due:-3d") # 24 - cls.t("add 9g due:-2d") # 25 - cls.t("add 9h due:-1d") # 26 - cls.t("add 9i due:now") # 27 - cls.t("add 9j due:25h") # 28 - cls.t("add 9k due:49h") # 29 - cls.t("add 9l due:73h") # 30 - cls.t("add 9m due:97h") # 31 - cls.t("add 9n due:121h") # 32 - cls.t("add 9o due:145h") # 33 - cls.t("add 9p due:169h") # 34 - cls.t("add 9q due:193h") # 35 - cls.t("add 9r due:217h") # 36 - cls.t("add 9s due:241h") # 37 - cls.t("add 9t due:265h") # 38 - cls.t("add 9u due:289h") # 39 - cls.t("add 9v due:313h") # 40 - cls.t("add 9w due:337h") # 41 - cls.t("add 9x due:361h") # 42 + cls.t("add 9a due:-10d") # 19 + cls.t("add 9b due:-7d") # 20 + cls.t("add 9c due:-6d") # 21 + cls.t("add 9d due:-5d") # 22 + cls.t("add 9e due:-4d") # 23 + cls.t("add 9f due:-3d") # 24 + cls.t("add 9g due:-2d") # 25 + cls.t("add 9h due:-1d") # 26 + cls.t("add 9i due:now") # 27 + cls.t("add 9j due:25h") # 28 + cls.t("add 9k due:49h") # 29 + cls.t("add 9l due:73h") # 30 + cls.t("add 9m due:97h") # 31 + cls.t("add 9n due:121h") # 32 + cls.t("add 9o due:145h") # 33 + cls.t("add 9p due:169h") # 34 + cls.t("add 9q due:193h") # 35 + cls.t("add 9r due:217h") # 36 + cls.t("add 9s due:241h") # 37 + cls.t("add 9t due:265h") # 38 + cls.t("add 9u due:289h") # 39 + cls.t("add 9v due:313h") # 40 + cls.t("add 9w due:337h") # 41 + cls.t("add 9x due:361h") # 42 - cls.t("add 10a project:PROJECT") # 43 + cls.t("add 10a project:PROJECT") # 43 - cls.t("add 11a +TAG") # 44 + cls.t("add 11a +TAG") # 44 - cls.t("add 12a scheduled:30d") # 45 - cls.t("add 12b scheduled:yesterday") # 46 + cls.t("add 12a scheduled:30d") # 45 + cls.t("add 12b scheduled:yesterday") # 46 - cls.t("add 13 pri:H") # 47 + cls.t("add 13 pri:H") # 47 def assertApproximately(self, target, value): """Verify that the number in 'value' is within the range""" @@ -264,7 +264,9 @@ class TestUrgency(TestCase): def test_urgency_coefficient_override(self): """Verify urgency coefficient override""" - code, out, err = self.t("rc.urgency.uda.priority.H.coefficient:0.01234 _get 47.urgency") + code, out, err = self.t( + "rc.urgency.uda.priority.H.coefficient:0.01234 _get 47.urgency" + ) self.assertApproximately(0.01234, out) def test_urgency_no_task(self): @@ -298,13 +300,13 @@ class TestBug837(TestCase): def test_unblocked_urgency(self): """837: Verify urgency goes to zero after unblocking - Bug 837: When a task is completed, tasks that depended upon it do not - have the correct urgency and depend on 0 when edited + Bug 837: When a task is completed, tasks that depended upon it do not + have the correct urgency and depend on 0 when edited """ self.t("add one") self.t("add two dep:1") - self.t("list") # GC/handleRecurrence + self.t("list") # GC/handleRecurrence code, out, err = self.t("_get 1.urgency") self.assertEqual("8\n", out) @@ -313,7 +315,7 @@ class TestBug837(TestCase): self.assertEqual("-5\n", out) self.t("1 done") - self.t("list") # GC/handleRecurrence + self.t("list") # GC/handleRecurrence code, out, err = self.t("_get 1.urgency") self.assertEqual("0\n", out) @@ -321,6 +323,7 @@ class TestBug837(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/urgency_inherit.t b/test/urgency_inherit.test.py similarity index 99% rename from test/urgency_inherit.t rename to test/urgency_inherit.test.py index c446355be..3df1c42fb 100755 --- a/test/urgency_inherit.t +++ b/test/urgency_inherit.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -30,6 +29,7 @@ import sys import os import unittest import json + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -79,6 +79,7 @@ class TestUrgencyInherit(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/util.t.cpp b/test/util.t.cpp deleted file mode 100644 index 6f554f3f6..000000000 --- a/test/util.t.cpp +++ /dev/null @@ -1,91 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (19); - - // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); - - // TODO int confirm4 (const std::string&); - - // TODO const std::string uuid (); - - // TODO These are in feedback.cpp, not util.cpp. - Task left; - left.set ("zero", "0"); - left.set ("one", 1); - left.set ("two", 2); - - Task right; - right.set ("zero", "00"); - right.set ("two", 2); - right.set ("three", 3); - - Task rightAgain (right); - - std::string output = left.diff (right); - t.ok (!(left == right), "Detected changes"); - t.ok (output.find ("Zero will be changed from '0' to '00'") != std::string::npos, "Detected change zero:0 -> zero:00"); - t.ok (output.find ("One will be deleted") != std::string::npos, "Detected deletion one:1 ->"); - t.ok (output.find ("Two") == std::string::npos, "Detected no change two:2 -> two:2"); - t.ok (output.find ("Three will be set to '3'") != std::string::npos, "Detected addition -> three:3"); - - output = right.diff (rightAgain); - t.ok (output.find ("No changes will be made") != std::string::npos, "No changes detected"); - - // std::vector indentProject (const std::string&, const std::string whitespace=" ", char delimiter='.'); - t.is (indentProject (""), "", "indentProject '' -> ''"); - t.is (indentProject ("one"), "one", "indentProject 'one' -> 'one'"); - t.is (indentProject ("one.two"), " two", "indentProject 'one.two' -> ' two'"); - t.is (indentProject ("one.two.three"), " three", "indentProject 'one.two.three' -> ' three'"); - - // bool nontrivial (const std::string&); - t.notok (nontrivial (""), "nontrivial '' -> false"); - t.notok (nontrivial (" "), "nontrivial ' ' -> false"); - t.notok (nontrivial ("\t\t"), "nontrivial '\\t\\t' -> false"); - t.notok (nontrivial (" \t \t"), "nontrivial ' \\t \\t' -> false"); - t.ok (nontrivial ("a"), "nontrivial 'a' -> true"); - t.ok (nontrivial (" a"), "nontrivial ' a' -> true"); - t.ok (nontrivial ("a "), "nontrivial 'a ' -> true"); - t.ok (nontrivial (" \t\ta"), "nontrivial ' \\t\\ta' -> true"); - t.ok (nontrivial ("a\t\t "), "nontrivial 'a\\t\\t ' -> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// - diff --git a/test/util_test.cpp b/test/util_test.cpp new file mode 100644 index 000000000..43a9c9bcd --- /dev/null +++ b/test/util_test.cpp @@ -0,0 +1,103 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(19); + Context context; + Context::setContext(&context); + + // Ensure environment has no influence. + unsetenv("TASKDATA"); + unsetenv("TASKRC"); + + // TODO int confirm4 (const std::string&); + + // TODO const std::string uuid (); + + // TODO These are in feedback.cpp, not util.cpp. + Task left; + left.set("zero", "0"); + left.set("one", 1); + left.set("two", 2); + + Task right; + right.set("zero", "00"); + right.set("two", 2); + right.set("three", 3); + + Task rightAgain(right); + + std::string output = left.diff(right); + t.ok(!(left == right), "Detected changes"); + t.ok(output.find("Zero will be changed from '0' to '00'") != std::string::npos, + "Detected change zero:0 -> zero:00"); + t.ok(output.find("One will be deleted") != std::string::npos, "Detected deletion one:1 ->"); + t.ok(output.find("Two") == std::string::npos, "Detected no change two:2 -> two:2"); + t.ok(output.find("Three will be set to '3'") != std::string::npos, + "Detected addition -> three:3"); + + output = right.diff(rightAgain); + t.ok(output.find("No changes will be made") != std::string::npos, "No changes detected"); + + // std::vector indentProject (const std::string&, const std::string whitespace=" ", + // char delimiter='.'); + t.is(indentProject(""), "", "indentProject '' -> ''"); + t.is(indentProject("one"), "one", "indentProject 'one' -> 'one'"); + t.is(indentProject("one.two"), " two", "indentProject 'one.two' -> ' two'"); + t.is(indentProject("one.two.three"), " three", "indentProject 'one.two.three' -> ' three'"); + + // bool nontrivial (const std::string&); + t.notok(nontrivial(""), "nontrivial '' -> false"); + t.notok(nontrivial(" "), "nontrivial ' ' -> false"); + t.notok(nontrivial("\t\t"), "nontrivial '\\t\\t' -> false"); + t.notok(nontrivial(" \t \t"), "nontrivial ' \\t \\t' -> false"); + t.ok(nontrivial("a"), "nontrivial 'a' -> true"); + t.ok(nontrivial(" a"), "nontrivial ' a' -> true"); + t.ok(nontrivial("a "), "nontrivial 'a ' -> true"); + t.ok(nontrivial(" \t\ta"), "nontrivial ' \\t\\ta' -> true"); + t.ok(nontrivial("a\t\t "), "nontrivial 'a\\t\\t ' -> true"); + + Datetime dt(1234526400); + Datetime max(std::numeric_limits::max()); + t.ok(checked_add_datetime(dt, 10).has_value(), "small delta"); + t.ok(!checked_add_datetime(dt, 0x100000000).has_value(), "delta > 32bit"); + t.ok(!checked_add_datetime(max, 1).has_value(), "huge base time"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/uuid.t b/test/uuid.test.py similarity index 87% rename from test/uuid.t rename to test/uuid.test.py index 55fe20169..5227cb616 100755 --- a/test/uuid.t +++ b/test/uuid.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -41,7 +41,9 @@ class TestUUID(TestCase): self.t.config("dateformat", "m/d/Y") - self.t("import -", input="""[ + self.t( + "import -", + input="""[ {"description":"one", "entry":"1315260230", "status":"pending", "uuid":"9deed7ca-843d-4259-b2c4-40ce73e8e4f3"}, {"description":"two", "entry":"1315260230", "status":"pending", "uuid":"0f4c83d2-552f-4108-ae3f-ccc7959f84a3"}, {"description":"three", "entry":"1315260230", "status":"pending", "uuid":"aa4abef1-1dc5-4a43-b6a0-7872df3094bb"}, @@ -52,7 +54,8 @@ class TestUUID(TestCase): {"description":"seven", "end":"1315338826", "entry":"1315338726", "status":"completed", "uuid":"abcdefab-abcd-abcd-abcd-abcdefabcdef"}, {"description":"eenndd", "end":"1315335841", "entry":"1315335841", "start":"1315338516", "status":"completed", "uuid":"727baa6c-65b8-485e-a810-e133e3cd83dc"}, {"description":"UUNNDDOO", "end":"1315338626", "entry":"1315338626", "status":"completed", "uuid":"c1361003-948e-43e8-85c8-15d28dc3c71c"} - ]""") + ]""", + ) def _config_unittest_report(self): self.t.config("report.unittest.columns", "id,entry,start,description") @@ -174,42 +177,6 @@ class TestUUID(TestCase): self.assertIn('"description":"seven"', out) -class TestUUIDuplicates(TestCase): - def setUp(self): - """Executed before each test in the class""" - self.t = Task() - - def test_uuid_duplicates_dupe(self): - """Verify that duplicating tasks does not create duplicate UUIDs""" - self.t("add simple") - self.t("1 duplicate") - - uuids = list() - for id in range(1,3): - code, out, err = self.t("_get %d.uuid" % id) - uuids.append(out.strip()) - - self.assertEqual(len(uuids), len(set(uuids))) - - code, out, err = self.t("diag") - self.assertIn("No duplicates found", out) - - def test_uuid_duplicates_recurrence(self): - """Verify that recurring tasks do not create duplicate UUIDs""" - print(self.t("add periodic recur:daily due:yesterday")) - self.t("list") # GC/handleRecurrence - - uuids = list() - for id in range(1,5): - code, out, err = self.t("_get %d.uuid" % id) - uuids.append(out.strip()) - - self.assertEqual(len(uuids), len(set(uuids))) - - code, out, err = self.t("diag") - self.assertIn("No duplicates found", out) - - class TestBug954(TestCase): def setUp(self): """Executed before each test in the class""" @@ -247,18 +214,19 @@ class TestFeature891(TestCase): def test_uuid_filter(self): """891: Test that a task is addressable using UUIDs of length 7 - 36""" - for i in range(35,7,-1): + for i in range(35, 7, -1): code, out, err = self.t(self.uuid[0:i] + " list") self.assertIn("one", out) self.assertNotIn("two", out) # TODO This should fail because a 7-character UUID is not a UUID, but # instead it blindly does nothing, and succeeds. Voodoo. - #code, out, err = self.t(self.uuid[0:6] + " list") + # code, out, err = self.t(self.uuid[0:6] + " list") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/variant_add.t.cpp b/test/variant_add.t.cpp deleted file mode 100644 index 56dc8ab46..000000000 --- a/test/variant_add.t.cpp +++ /dev/null @@ -1,239 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (80); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean + boolean -> ERROR - try {Variant v00 = v0 + v0; t.fail ("true + true --> error");} - catch (...) {t.pass ("true + true --> error");} - - // boolean + integer -> integer - Variant v01 = v0 + v1; - t.is (v01.type (), Variant::type_integer, "true + 42 --> integer"); - t.is (v01.get_integer (), 43, "true + 42 --> 43"); - - // boolean + real -> real - Variant v02 = v0 + v2; - t.is (v02.type (), Variant::type_real, "true + 3.14 --> real"); - t.is (v02.get_real (), 4.14, EPSILON, "true + 3.14 --> 4.14"); - - // boolean + string -> string - Variant v03 = v0 + v3; - t.is (v03.type (), Variant::type_string, "true + foo --> string"); - t.is (v03.get_string (), "truefoo", "true + foo --> truefoo"); - - // boolean + date -> date - Variant v04 = v0 + v4; - t.is (v04.type (), Variant::type_date, "true + 1234567890 --> date"); - t.is (v04.get_date (), "1234567891", "true + 1234567890 --> 1234567891"); - - // boolean + duration -> duration - Variant v05 = v0 + v5; - t.is (v05.type (), Variant::type_duration, "true + 1200 --> duration"); - t.is (v05.get_duration (), "1201", "true + 1200 --> 1201"); - - // integer + boolean -> integer - Variant v10 = v1 + v0; - t.is (v10.type (), Variant::type_integer, "42 + true --> integer"); - t.is (v10.get_integer (), 43, "42 + true --> 43"); - - // integer + integer -> integer - Variant v11 = v1 + v1; - t.is (v11.type (), Variant::type_integer, "42 + 42 --> integer"); - t.is (v11.get_integer (), 84, "42 + 42 --> 84"); - - // integer + real -> real - Variant v12 = v1 + v2; - t.is (v12.type (), Variant::type_real, "42 + 3.14 --> real"); - t.is (v12.get_real (), 45.14, EPSILON, "42 + 3.14 --> 45.14"); - - // integer + string -> string - Variant v13 = v1 + v3; - t.is (v13.type (), Variant::type_string, "42 + foo --> string"); - t.is (v13.get_string (), "42foo", "42 + foo --> 42foo"); - - // integer + date -> date - Variant v14 = v1 + v4; - t.is (v14.type (), Variant::type_date, "42 + 1234567890 --> date"); - t.is (v14.get_date (), 1234567932, "42 + 1234567890 --> 1234567932"); - - // integer + duration -> duration - Variant v15 = v1 + v5; - t.is (v15.type (), Variant::type_duration, "42 + 1200 --> duration"); - t.is (v15.get_duration (), 1242, "42 + 1200 --> 1242"); - - // real + boolean -> real - Variant v20 = v2 + v0; - t.is (v20.type (), Variant::type_real, "3.14 + true --> real"); - t.is (v20.get_real (), 4.14, EPSILON, "3.14 + true --> 4.14"); - - // real + integer -> real - Variant v21 = v2 + v1; - t.is (v21.type (), Variant::type_real, "3.14 + 42 --> real"); - t.is (v21.get_real (), 45.14, EPSILON, "3.14 + 42 --> 45.14"); - - // real + real -> real - Variant v22 = v2 + v2; - t.is (v22.type (), Variant::type_real, "3.14 + 3.14 --> real"); - t.is (v22.get_real (), 6.28, EPSILON, "3.14 + 3.14 --> 6.28"); - - // real + string -> string - Variant v23 = v2 + v3; - t.is (v23.type (), Variant::type_string, "3.14 + foo --> string"); - t.is (v23.get_string (), "3.14foo", "3.14 + foo --> 3.14foo"); - - // real + date -> date - Variant v24 = v2 + v4; - t.is (v24.type (), Variant::type_date, "3.14 + 1234567890 --> date"); - t.is (v24.get_date (), 1234567893, "3.14 + 1234567890 --> 1234567893"); - - // real + duration -> duration - Variant v25 = v2 + v5; - t.is (v25.type (), Variant::type_duration, "3.14 + 1200 --> duration"); - t.is (v25.get_duration (), 1203, "3.14 + 1200 --> 1203"); - - // string + boolean -> string - Variant v30 = v3 + v0; - t.is (v30.type (), Variant::type_string, "foo + true --> string"); - t.is (v30.get_string (), "footrue", "foo + true --> footrue"); - - // string + integer -> string - Variant v31 = v3 + v1; - t.is (v31.type (), Variant::type_string, "foo + 42 --> string"); - t.is (v31.get_string (), "foo42", "foo + 42 --> foo42"); - - // string + real -> string - Variant v32 = v3 + v2; - t.is (v32.type (), Variant::type_string, "foo + 3.14 --> string"); - t.is (v32.get_string (), "foo3.14", "foo + 3.14 --> foo3.14"); - - // string + string -> string - Variant v33 = v3 + v3; - t.is (v33.type (), Variant::type_string, "foo + foo --> string"); - t.is (v33.get_string (), "foofoo", "foo + foo --> foofoo"); - - // string + date -> string - Variant v34 = v3 + v4; - t.is (v34.type (), Variant::type_string, "foo + 1234567890 --> string"); - std::string s = v34.get_string (); - t.is ((int)s[7], (int)'-', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - t.is ((int)s[10], (int)'-', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - t.is ((int)s[13], (int)'T', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - t.is ((int)s[16], (int)':', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - t.is ((int)s[19], (int)':', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - t.is ((int)s.length (), 22, "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); - - // string + duration -> string - Variant v35 = v3 + v5; - t.is (v35.type (), Variant::type_string, "foo + 1200 --> string"); - t.is (v35.get_string (), "fooPT20M", "foo + 1200 --> fooPT20M"); - - // date + boolean -> date - Variant v40 = v4 + v0; - t.is (v40.type (), Variant::type_date, "1234567890 + true --> date"); - t.is (v40.get_date (), 1234567891, "1234567890 + true --> 1234567891"); - - // date + integer -> date - Variant v41 = v4 + v1; - t.is (v41.type (), Variant::type_date, "1234567890 + 42 --> date"); - t.is (v41.get_date (), 1234567932, "1234567890 + 42 --> 1234567932"); - - // date + real -> date - Variant v42 = v4 + v2; - t.is (v42.type (), Variant::type_date, "1234567890 + 3.14 --> date"); - t.is (v42.get_date (), 1234567893, "1234567890 + 3.14 --> 1234567893"); - - // date + string -> string - Variant v43 = v4 + v3; - t.is (v43.type (), Variant::type_string, "1234567890 + foo --> string"); - s = v43.get_string (); - t.is ((int)s[4], (int)'-', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - t.is ((int)s[7], (int)'-', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - t.is ((int)s[10], (int)'T', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - t.is ((int)s[13], (int)':', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - t.is ((int)s[16], (int)':', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - t.is ((int)s.length (), 22, "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); - - // date + date -> ERROR - try {Variant v44 = v4 + v4; t.fail ("1234567890 + 1234567890 --> error");} - catch (...) {t.pass ("1234567890 + 1234567890 --> error");} - - // date + duration -> date - Variant v45 = v4 + v5; - t.is (v45.type (), Variant::type_date, "1234567890 + 1200 --> date"); - t.is (v45.get_date (), 1234569090, "1234567890 + 1200 --> 1234569090"); - - // duration + boolean -> duration - Variant v50 = v5 + v0; - t.is (v50.type (), Variant::type_duration, "1200 + true --> duration"); - t.is (v50.get_duration (), 1201, "1200 + true --> 1201"); - - // duration + integer -> duration - Variant v51 = v5 + v1; - t.is (v51.type (), Variant::type_duration, "1200 + 42 --> duration"); - t.is (v51.get_duration (), 1242, "1200 + 42 --> 1242"); - - // duration + real -> duration - Variant v52 = v5 + v2; - t.is (v52.type (), Variant::type_duration, "1200 + 3.14 --> duration"); - t.is (v52.get_duration (), 1203, "1200 + 3.14 --> 1203"); - - // duration + string -> string - Variant v53 = v5 + v3; - t.is (v53.type (), Variant::type_string, "1200 + foo --> string"); - t.is (v53.get_string (), "PT20Mfoo", "1200 + foo --> PT20Mfoo"); - - // duration + date -> date - Variant v54 = v5 + v4; - t.is (v54.type (), Variant::type_date, "1200 + 1234567890 --> date"); - t.is (v54.get_date (), 1234569090, "1200 + 1234567890 --> 1234569090"); - - // duration + duration -> duration - Variant v55 = v5 + v5; - t.is (v55.type (), Variant::type_duration, "1200 + 1200 --> duration"); - t.is (v55.get_duration (), 2400, "1200 + 1200 --> 2400"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_add_test.cpp b/test/variant_add_test.cpp new file mode 100644 index 000000000..b17a3812c --- /dev/null +++ b/test/variant_add_test.cpp @@ -0,0 +1,249 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(80); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean + boolean -> ERROR + try { + Variant v00 = v0 + v0; + t.fail("true + true --> error"); + } catch (...) { + t.pass("true + true --> error"); + } + + // boolean + integer -> integer + Variant v01 = v0 + v1; + t.is(v01.type(), Variant::type_integer, "true + 42 --> integer"); + t.is(v01.get_integer(), 43, "true + 42 --> 43"); + + // boolean + real -> real + Variant v02 = v0 + v2; + t.is(v02.type(), Variant::type_real, "true + 3.14 --> real"); + t.is(v02.get_real(), 4.14, EPSILON, "true + 3.14 --> 4.14"); + + // boolean + string -> string + Variant v03 = v0 + v3; + t.is(v03.type(), Variant::type_string, "true + foo --> string"); + t.is(v03.get_string(), "truefoo", "true + foo --> truefoo"); + + // boolean + date -> date + Variant v04 = v0 + v4; + t.is(v04.type(), Variant::type_date, "true + 1234567890 --> date"); + t.is(v04.get_date(), "1234567891", "true + 1234567890 --> 1234567891"); + + // boolean + duration -> duration + Variant v05 = v0 + v5; + t.is(v05.type(), Variant::type_duration, "true + 1200 --> duration"); + t.is(v05.get_duration(), "1201", "true + 1200 --> 1201"); + + // integer + boolean -> integer + Variant v10 = v1 + v0; + t.is(v10.type(), Variant::type_integer, "42 + true --> integer"); + t.is(v10.get_integer(), 43, "42 + true --> 43"); + + // integer + integer -> integer + Variant v11 = v1 + v1; + t.is(v11.type(), Variant::type_integer, "42 + 42 --> integer"); + t.is(v11.get_integer(), 84, "42 + 42 --> 84"); + + // integer + real -> real + Variant v12 = v1 + v2; + t.is(v12.type(), Variant::type_real, "42 + 3.14 --> real"); + t.is(v12.get_real(), 45.14, EPSILON, "42 + 3.14 --> 45.14"); + + // integer + string -> string + Variant v13 = v1 + v3; + t.is(v13.type(), Variant::type_string, "42 + foo --> string"); + t.is(v13.get_string(), "42foo", "42 + foo --> 42foo"); + + // integer + date -> date + Variant v14 = v1 + v4; + t.is(v14.type(), Variant::type_date, "42 + 1234567890 --> date"); + t.is(v14.get_date(), 1234567932, "42 + 1234567890 --> 1234567932"); + + // integer + duration -> duration + Variant v15 = v1 + v5; + t.is(v15.type(), Variant::type_duration, "42 + 1200 --> duration"); + t.is(v15.get_duration(), 1242, "42 + 1200 --> 1242"); + + // real + boolean -> real + Variant v20 = v2 + v0; + t.is(v20.type(), Variant::type_real, "3.14 + true --> real"); + t.is(v20.get_real(), 4.14, EPSILON, "3.14 + true --> 4.14"); + + // real + integer -> real + Variant v21 = v2 + v1; + t.is(v21.type(), Variant::type_real, "3.14 + 42 --> real"); + t.is(v21.get_real(), 45.14, EPSILON, "3.14 + 42 --> 45.14"); + + // real + real -> real + Variant v22 = v2 + v2; + t.is(v22.type(), Variant::type_real, "3.14 + 3.14 --> real"); + t.is(v22.get_real(), 6.28, EPSILON, "3.14 + 3.14 --> 6.28"); + + // real + string -> string + Variant v23 = v2 + v3; + t.is(v23.type(), Variant::type_string, "3.14 + foo --> string"); + t.is(v23.get_string(), "3.14foo", "3.14 + foo --> 3.14foo"); + + // real + date -> date + Variant v24 = v2 + v4; + t.is(v24.type(), Variant::type_date, "3.14 + 1234567890 --> date"); + t.is(v24.get_date(), 1234567893, "3.14 + 1234567890 --> 1234567893"); + + // real + duration -> duration + Variant v25 = v2 + v5; + t.is(v25.type(), Variant::type_duration, "3.14 + 1200 --> duration"); + t.is(v25.get_duration(), 1203, "3.14 + 1200 --> 1203"); + + // string + boolean -> string + Variant v30 = v3 + v0; + t.is(v30.type(), Variant::type_string, "foo + true --> string"); + t.is(v30.get_string(), "footrue", "foo + true --> footrue"); + + // string + integer -> string + Variant v31 = v3 + v1; + t.is(v31.type(), Variant::type_string, "foo + 42 --> string"); + t.is(v31.get_string(), "foo42", "foo + 42 --> foo42"); + + // string + real -> string + Variant v32 = v3 + v2; + t.is(v32.type(), Variant::type_string, "foo + 3.14 --> string"); + t.is(v32.get_string(), "foo3.14", "foo + 3.14 --> foo3.14"); + + // string + string -> string + Variant v33 = v3 + v3; + t.is(v33.type(), Variant::type_string, "foo + foo --> string"); + t.is(v33.get_string(), "foofoo", "foo + foo --> foofoo"); + + // string + date -> string + Variant v34 = v3 + v4; + t.is(v34.type(), Variant::type_string, "foo + 1234567890 --> string"); + std::string s = v34.get_string(); + t.is((int)s[7], (int)'-', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + t.is((int)s[10], (int)'-', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + t.is((int)s[13], (int)'T', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + t.is((int)s[16], (int)':', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + t.is((int)s[19], (int)':', "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + t.is((int)s.length(), 22, "foo + 1234567890 --> fooYYYY-MM-DDThh:mm:ss"); + + // string + duration -> string + Variant v35 = v3 + v5; + t.is(v35.type(), Variant::type_string, "foo + 1200 --> string"); + t.is(v35.get_string(), "fooPT20M", "foo + 1200 --> fooPT20M"); + + // date + boolean -> date + Variant v40 = v4 + v0; + t.is(v40.type(), Variant::type_date, "1234567890 + true --> date"); + t.is(v40.get_date(), 1234567891, "1234567890 + true --> 1234567891"); + + // date + integer -> date + Variant v41 = v4 + v1; + t.is(v41.type(), Variant::type_date, "1234567890 + 42 --> date"); + t.is(v41.get_date(), 1234567932, "1234567890 + 42 --> 1234567932"); + + // date + real -> date + Variant v42 = v4 + v2; + t.is(v42.type(), Variant::type_date, "1234567890 + 3.14 --> date"); + t.is(v42.get_date(), 1234567893, "1234567890 + 3.14 --> 1234567893"); + + // date + string -> string + Variant v43 = v4 + v3; + t.is(v43.type(), Variant::type_string, "1234567890 + foo --> string"); + s = v43.get_string(); + t.is((int)s[4], (int)'-', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + t.is((int)s[7], (int)'-', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + t.is((int)s[10], (int)'T', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + t.is((int)s[13], (int)':', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + t.is((int)s[16], (int)':', "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + t.is((int)s.length(), 22, "1234567890 + foo --> YYYY-MM-DDThh:mm:ssfoo"); + + // date + date -> ERROR + try { + Variant v44 = v4 + v4; + t.fail("1234567890 + 1234567890 --> error"); + } catch (...) { + t.pass("1234567890 + 1234567890 --> error"); + } + + // date + duration -> date + Variant v45 = v4 + v5; + t.is(v45.type(), Variant::type_date, "1234567890 + 1200 --> date"); + t.is(v45.get_date(), 1234569090, "1234567890 + 1200 --> 1234569090"); + + // duration + boolean -> duration + Variant v50 = v5 + v0; + t.is(v50.type(), Variant::type_duration, "1200 + true --> duration"); + t.is(v50.get_duration(), 1201, "1200 + true --> 1201"); + + // duration + integer -> duration + Variant v51 = v5 + v1; + t.is(v51.type(), Variant::type_duration, "1200 + 42 --> duration"); + t.is(v51.get_duration(), 1242, "1200 + 42 --> 1242"); + + // duration + real -> duration + Variant v52 = v5 + v2; + t.is(v52.type(), Variant::type_duration, "1200 + 3.14 --> duration"); + t.is(v52.get_duration(), 1203, "1200 + 3.14 --> 1203"); + + // duration + string -> string + Variant v53 = v5 + v3; + t.is(v53.type(), Variant::type_string, "1200 + foo --> string"); + t.is(v53.get_string(), "PT20Mfoo", "1200 + foo --> PT20Mfoo"); + + // duration + date -> date + Variant v54 = v5 + v4; + t.is(v54.type(), Variant::type_date, "1200 + 1234567890 --> date"); + t.is(v54.get_date(), 1234569090, "1200 + 1234567890 --> 1234569090"); + + // duration + duration -> duration + Variant v55 = v5 + v5; + t.is(v55.type(), Variant::type_duration, "1200 + 1200 --> duration"); + t.is(v55.get_duration(), 2400, "1200 + 1200 --> 2400"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_and.t.cpp b/test/variant_and.t.cpp deleted file mode 100644 index 9963556e8..000000000 --- a/test/variant_and.t.cpp +++ /dev/null @@ -1,199 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (76); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // Truth table. - Variant vFalse (false); - Variant vTrue (true); - t.is (vFalse && vFalse, false, "false && false --> false"); - t.is (vFalse && vTrue, false, "false && true --> false"); - t.is (vTrue && vFalse, false, "true && false --> false"); - t.is (vTrue && vTrue, true, "true && true --> true"); - - Variant v00 = v0 && v0; - t.is (v00.type (), Variant::type_boolean, "true && true --> boolean"); - t.is (v00.get_bool (), true, "true && true --> true"); - - Variant v01 = v0 && v1; - t.is (v01.type (), Variant::type_boolean, "true && 42 --> boolean"); - t.is (v01.get_bool (), true, "true && 42 --> true"); - - Variant v02 = v0 && v2; - t.is (v02.type (), Variant::type_boolean, "true && 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true && 3.14 --> true"); - - Variant v03 = v0 && v3; - t.is (v03.type (), Variant::type_boolean, "true && 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true && 'foo' --> true"); - - Variant v04 = v0 && v4; - t.is (v04.type (), Variant::type_boolean, "true && 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true && 1234567890 --> true"); - - Variant v05 = v0 && v5; - t.is (v05.type (), Variant::type_boolean, "true && 1200 --> boolean"); - t.is (v05.get_bool (), true, "true && 1200 --> true"); - - Variant v10 = v1 && v0; - t.is (v10.type (), Variant::type_boolean, "42 && true --> boolean"); - t.is (v10.get_bool (), true, "42 && true --> true"); - - Variant v11 = v1 && v1; - t.is (v11.type (), Variant::type_boolean, "42 && 42 --> boolean"); - t.is (v11.get_bool (), true, "42 && 42 --> true"); - - Variant v12 = v1 && v2; - t.is (v12.type (), Variant::type_boolean, "42 && 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 && 3.14 --> true"); - - Variant v13 = v1 && v3; - t.is (v13.type (), Variant::type_boolean, "42 && 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 && 'foo' --> true"); - - Variant v14 = v1 && v4; - t.is (v04.type (), Variant::type_boolean, "42 && 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 && 1234567890 --> true"); - - Variant v15 = v1 && v5; - t.is (v15.type (), Variant::type_boolean, "42 && 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 && 1200 --> true"); - - Variant v20 = v2 && v0; - t.is (v20.type (), Variant::type_boolean, "3.14 && true --> boolean"); - t.is (v20.get_bool (), true, "3.14 && true --> true"); - - Variant v21 = v2 && v1; - t.is (v21.type (), Variant::type_boolean, "3.14 && 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 && 42 --> true"); - - Variant v22 = v2 && v2; - t.is (v22.type (), Variant::type_boolean, "3.14 && 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 && 3.14 --> true"); - - Variant v23 = v2 && v3; - t.is (v23.type (), Variant::type_boolean, "3.14 && 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 && 'foo' --> true"); - - Variant v24 = v2 && v4; - t.is (v24.type (), Variant::type_boolean, "3.14 && 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 && 1234567890 --> true"); - - Variant v25 = v2 && v5; - t.is (v25.type (), Variant::type_boolean, "3.14 && 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 && 1200 --> true"); - - Variant v30 = v3 && v0; - t.is (v30.type (), Variant::type_boolean, "'foo' && true --> boolean"); - t.is (v30.get_bool (), true, "'foo' && true --> true"); - - Variant v31 = v3 && v1; - t.is (v31.type (), Variant::type_boolean, "'foo' && 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' && 42 --> true"); - - Variant v32 = v3 && v2; - t.is (v32.type (), Variant::type_boolean, "'foo' && 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' && 3.14 --> true"); - - Variant v33 = v3 && v3; - t.is (v33.type (), Variant::type_boolean, "'foo' && 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' && 'foo' --> true"); - - Variant v34 = v3 && v4; - t.is (v34.type (), Variant::type_boolean, "'foo' && 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' && 1234567890 --> true"); - - Variant v35 = v3 && v5; - t.is (v35.type (), Variant::type_boolean, "'foo' && 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' && 1200 --> true"); - - Variant v40 = v4 && v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 && true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 && true --> true"); - - Variant v41 = v4 && v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 && 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 && 42 --> true"); - - Variant v42 = v4 && v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 && 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 && 3.14 --> true"); - - Variant v43 = v4 && v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 && 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 && 'foo' --> true"); - - Variant v44 = v4 && v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 && 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 && 1234567890 --> true"); - - Variant v45 = v4 && v5; - t.is (v45.type (), Variant::type_boolean, "1234567890 && 1200 --> boolean"); - t.is (v45.get_bool (), true, "1234567890 && 1200 --> true"); - - Variant v50 = v5 && v0; - t.is (v50.type (), Variant::type_boolean, "1200 && true --> boolean"); - t.is (v50.get_bool (), true, "1200 && true --> true"); - - Variant v51 = v5 && v1; - t.is (v51.type (), Variant::type_boolean, "1200 && 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 && 42 --> true"); - - Variant v52 = v5 && v2; - t.is (v52.type (), Variant::type_boolean, "1200 && 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 && 3.14 --> true"); - - Variant v53 = v5 && v3; - t.is (v53.type (), Variant::type_boolean, "1200 && 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 && 'foo' --> true"); - - Variant v54 = v5 && v4; - t.is (v04.type (), Variant::type_boolean, "1200 && 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "1200 && 1234567890 --> true"); - - Variant v55 = v5 && v5; - t.is (v55.type (), Variant::type_boolean, "1200 && 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 && 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_and_test.cpp b/test/variant_and_test.cpp new file mode 100644 index 000000000..97196ea78 --- /dev/null +++ b/test/variant_and_test.cpp @@ -0,0 +1,201 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(76); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // Truth table. + Variant vFalse(false); + Variant vTrue(true); + t.is(vFalse && vFalse, false, "false && false --> false"); + t.is(vFalse && vTrue, false, "false && true --> false"); + t.is(vTrue && vFalse, false, "true && false --> false"); + t.is(vTrue && vTrue, true, "true && true --> true"); + + Variant v00 = v0 && v0; + t.is(v00.type(), Variant::type_boolean, "true && true --> boolean"); + t.is(v00.get_bool(), true, "true && true --> true"); + + Variant v01 = v0 && v1; + t.is(v01.type(), Variant::type_boolean, "true && 42 --> boolean"); + t.is(v01.get_bool(), true, "true && 42 --> true"); + + Variant v02 = v0 && v2; + t.is(v02.type(), Variant::type_boolean, "true && 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true && 3.14 --> true"); + + Variant v03 = v0 && v3; + t.is(v03.type(), Variant::type_boolean, "true && 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true && 'foo' --> true"); + + Variant v04 = v0 && v4; + t.is(v04.type(), Variant::type_boolean, "true && 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true && 1234567890 --> true"); + + Variant v05 = v0 && v5; + t.is(v05.type(), Variant::type_boolean, "true && 1200 --> boolean"); + t.is(v05.get_bool(), true, "true && 1200 --> true"); + + Variant v10 = v1 && v0; + t.is(v10.type(), Variant::type_boolean, "42 && true --> boolean"); + t.is(v10.get_bool(), true, "42 && true --> true"); + + Variant v11 = v1 && v1; + t.is(v11.type(), Variant::type_boolean, "42 && 42 --> boolean"); + t.is(v11.get_bool(), true, "42 && 42 --> true"); + + Variant v12 = v1 && v2; + t.is(v12.type(), Variant::type_boolean, "42 && 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 && 3.14 --> true"); + + Variant v13 = v1 && v3; + t.is(v13.type(), Variant::type_boolean, "42 && 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 && 'foo' --> true"); + + Variant v14 = v1 && v4; + t.is(v04.type(), Variant::type_boolean, "42 && 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 && 1234567890 --> true"); + + Variant v15 = v1 && v5; + t.is(v15.type(), Variant::type_boolean, "42 && 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 && 1200 --> true"); + + Variant v20 = v2 && v0; + t.is(v20.type(), Variant::type_boolean, "3.14 && true --> boolean"); + t.is(v20.get_bool(), true, "3.14 && true --> true"); + + Variant v21 = v2 && v1; + t.is(v21.type(), Variant::type_boolean, "3.14 && 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 && 42 --> true"); + + Variant v22 = v2 && v2; + t.is(v22.type(), Variant::type_boolean, "3.14 && 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 && 3.14 --> true"); + + Variant v23 = v2 && v3; + t.is(v23.type(), Variant::type_boolean, "3.14 && 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 && 'foo' --> true"); + + Variant v24 = v2 && v4; + t.is(v24.type(), Variant::type_boolean, "3.14 && 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 && 1234567890 --> true"); + + Variant v25 = v2 && v5; + t.is(v25.type(), Variant::type_boolean, "3.14 && 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 && 1200 --> true"); + + Variant v30 = v3 && v0; + t.is(v30.type(), Variant::type_boolean, "'foo' && true --> boolean"); + t.is(v30.get_bool(), true, "'foo' && true --> true"); + + Variant v31 = v3 && v1; + t.is(v31.type(), Variant::type_boolean, "'foo' && 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' && 42 --> true"); + + Variant v32 = v3 && v2; + t.is(v32.type(), Variant::type_boolean, "'foo' && 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' && 3.14 --> true"); + + Variant v33 = v3 && v3; + t.is(v33.type(), Variant::type_boolean, "'foo' && 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' && 'foo' --> true"); + + Variant v34 = v3 && v4; + t.is(v34.type(), Variant::type_boolean, "'foo' && 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' && 1234567890 --> true"); + + Variant v35 = v3 && v5; + t.is(v35.type(), Variant::type_boolean, "'foo' && 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' && 1200 --> true"); + + Variant v40 = v4 && v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 && true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 && true --> true"); + + Variant v41 = v4 && v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 && 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 && 42 --> true"); + + Variant v42 = v4 && v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 && 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 && 3.14 --> true"); + + Variant v43 = v4 && v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 && 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 && 'foo' --> true"); + + Variant v44 = v4 && v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 && 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 && 1234567890 --> true"); + + Variant v45 = v4 && v5; + t.is(v45.type(), Variant::type_boolean, "1234567890 && 1200 --> boolean"); + t.is(v45.get_bool(), true, "1234567890 && 1200 --> true"); + + Variant v50 = v5 && v0; + t.is(v50.type(), Variant::type_boolean, "1200 && true --> boolean"); + t.is(v50.get_bool(), true, "1200 && true --> true"); + + Variant v51 = v5 && v1; + t.is(v51.type(), Variant::type_boolean, "1200 && 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 && 42 --> true"); + + Variant v52 = v5 && v2; + t.is(v52.type(), Variant::type_boolean, "1200 && 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 && 3.14 --> true"); + + Variant v53 = v5 && v3; + t.is(v53.type(), Variant::type_boolean, "1200 && 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 && 'foo' --> true"); + + Variant v54 = v5 && v4; + t.is(v04.type(), Variant::type_boolean, "1200 && 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "1200 && 1234567890 --> true"); + + Variant v55 = v5 && v5; + t.is(v55.type(), Variant::type_boolean, "1200 && 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 && 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_cast.t.cpp b/test/variant_cast.t.cpp deleted file mode 100644 index 8eb2c63cc..000000000 --- a/test/variant_cast.t.cpp +++ /dev/null @@ -1,259 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (81); - - time_t now = time (nullptr); - - try - { - // Variant::type_boolean --> * - Variant v00 (true); - v00.cast (Variant::type_boolean); - t.ok (v00.type () == Variant::type_boolean, "cast boolean --> boolean"); - t.ok (v00.get_bool () == true, "cast boolean --> boolean"); - - Variant v01 (true); - v01.cast (Variant::type_integer); - t.ok (v01.type () == Variant::type_integer, "cast boolean --> integer"); - t.ok (v01.get_integer () == 1, "cast boolean --> integer"); - - Variant v02 (true); - v02.cast (Variant::type_real); - t.ok (v02.type () == Variant::type_real, "cast boolean --> real"); - t.is (v02.get_real (), 1.0, EPSILON, "cast boolean --> real"); - - Variant v03 (true); - v03.cast (Variant::type_string); - t.ok (v03.type () == Variant::type_string, "cast boolean --> string"); - t.ok (v03.get_string () == "true", "cast boolean --> string"); - - Variant v04 (true); - v04.cast (Variant::type_date); - t.ok (v04.type () == Variant::type_date, "cast boolean --> date"); - t.is (v04.get_date (), 1, "cast boolean --> date"); - - Variant v05 (true); - v05.cast (Variant::type_duration); - t.ok (v05.type () == Variant::type_duration, "cast boolean --> duration"); - t.is (v05.get_duration (), 1, "cast boolean --> duration"); - - // Variant::type_integer --> * - Variant v10 (42); - v10.cast (Variant::type_boolean); - t.ok (v10.type () == Variant::type_boolean, "cast integer --> boolean"); - t.ok (v10.get_bool () == true, "cast integer --> boolean"); - - Variant v11 (42); - v11.cast (Variant::type_integer); - t.ok (v11.type () == Variant::type_integer, "cast integer --> integer"); - t.ok (v11.get_integer () == 42, "cast integer --> integer"); - - Variant v12 (42); - v12.cast (Variant::type_real); - t.ok (v12.type () == Variant::type_real, "cast integer --> real"); - t.is (v12.get_real (), 42.0, EPSILON, "cast integer --> real"); - - Variant v13 (42); - v13.cast (Variant::type_string); - t.is (v13.type (), Variant::type_string, "cast integer --> string"); - t.is (v13.get_string (), "42", "cast integer --> string"); - - Variant v14 (42); - v14.cast (Variant::type_date); - t.ok (v14.type () == Variant::type_date, "cast integer --> date"); - t.ok (v14.get_date () == 42, "cast integer --> date"); - - Variant v15 (42); - v15.cast (Variant::type_duration); - t.is (v15.type (), Variant::type_duration, "cast integer --> duration"); - t.is (v15.get_duration (), 42, "cast integer --> duration"); - - // Variant::type_real --> * - Variant v20 (3.14); - v20.cast (Variant::type_boolean); - t.ok (v20.type () == Variant::type_boolean, "cast real --> boolean"); - t.ok (v20.get_bool () == true, "cast real --> boolean"); - - Variant v21 (3.14); - v21.cast (Variant::type_integer); - t.ok (v21.type () == Variant::type_integer, "cast real --> integer"); - t.ok (v21.get_integer () == 3, "cast real --> integer"); - - Variant v22 (3.14); - v22.cast (Variant::type_real); - t.ok (v22.type () == Variant::type_real, "cast real --> real"); - t.is (v22.get_real (), 3.14, EPSILON, "cast real --> real"); - - Variant v23 (3.14); - v23.cast (Variant::type_string); - t.ok (v23.type () == Variant::type_string, "cast real --> string"); - t.ok (v23.get_string () == "3.14", "cast real --> string"); - - Variant v24 (3.14); - v24.cast (Variant::type_date); - t.ok (v24.type () == Variant::type_date, "cast real --> date"); - t.ok (v24.get_date () == 3, "cast real --> date"); - - Variant v25 (3.14); - v25.cast (Variant::type_duration); - t.ok (v25.type () == Variant::type_duration, "cast real --> duration"); - t.ok (v25.get_duration () == 3, "cast real --> duration"); - - // Variant::type_string --> * - Variant v30 ("foo"); - v30.cast (Variant::type_boolean); - t.ok (v30.type () == Variant::type_boolean, "cast string --> boolean"); - t.ok (v30.get_bool () == true, "cast string --> boolean"); - - Variant v31 ("42"); - v31.cast (Variant::type_integer); - t.ok (v31.type () == Variant::type_integer, "cast string --> integer"); - t.ok (v31.get_integer () == 42, "cast string --> integer"); - - Variant v31h ("0x20"); - v31h.cast (Variant::type_integer); - t.ok (v31h.type () == Variant::type_integer, "cast string(hex) --> integer"); - t.ok (v31h.get_integer () == 32, "cast string(hex) --> integer"); - - Variant v32 ("3.14"); - v32.cast (Variant::type_real); - t.ok (v32.type () == Variant::type_real, "cast string --> real"); - t.is (v32.get_real (), 3.14, EPSILON, "cast string --> real"); - - Variant v33 ("foo"); - v33.cast (Variant::type_string); - t.ok (v33.type () == Variant::type_string, "cast string --> string"); - t.ok (v33.get_string () == "foo", "cast string --> string"); - - Variant v34 ("2013-12-07T16:33:00-05:00"); - v34.cast (Variant::type_date); - t.ok (v34.type () == Variant::type_date, "cast string --> date"); - t.ok (v34.get_date () == 1386451980, "cast string --> date"); - - Variant v35 ("42 days"); - v35.cast (Variant::type_duration); - t.ok (v35.type () == Variant::type_duration, "cast string --> duration"); - t.is (v35.get_duration (), 3628800, "cast string --> duration"); - - Variant v35b ("P42D"); - v35b.cast (Variant::type_duration); - t.ok (v35b.type () == Variant::type_duration, "cast string --> duration"); - t.is (v35b.get_duration (), 3628800, "cast string --> duration"); - - // Variant::type_date --> * - Variant v40 ((time_t) 1234567890, Variant::type_date); - v40.cast (Variant::type_boolean); - t.ok (v40.type () == Variant::type_boolean, "cast date --> boolean"); - t.ok (v40.get_bool () == true, "cast date --> boolean"); - - Variant v41 ((time_t) 1234567890, Variant::type_date); - v41.cast (Variant::type_integer); - t.ok (v41.type () == Variant::type_integer, "cast date --> integer"); - t.ok (v41.get_integer () == 1234567890, "cast date --> integer"); - - Variant v42 ((time_t) 1234567890, Variant::type_date); - v42.cast (Variant::type_real); - t.ok (v42.type () == Variant::type_real, "cast date --> real"); - t.is (v42.get_real (), 1234567890.0, EPSILON, "cast date --> real"); - - // YYYY-MM-DDThh:mm:ss - // ^ ^ ^ ^ ^ - Variant v43 ((time_t) 1234567890, Variant::type_date); - v43.cast (Variant::type_string); - t.ok (v43.type () == Variant::type_string, "cast date --> string"); - std::string s = v43.get_string (); - t.is ((int)s[4], (int)'-', "cast date --> string"); - t.is ((int)s[7], (int)'-', "cast date --> string"); - t.is ((int)s[10], (int)'T', "cast date --> string"); - t.is ((int)s[13], (int)':', "cast date --> string"); - t.is ((int)s[16], (int)':', "cast date --> string"); - t.is ((int)s.length (), 19, "cast date --> string"); - - Variant v44 ((time_t) 1234567890, Variant::type_date); - v44.cast (Variant::type_date); - t.ok (v44.type () == Variant::type_date, "cast date --> date"); - t.ok (v44.get_date () == 1234567890, "cast date --> date"); - - Variant v45 ((time_t) 1234567890, Variant::type_date); - v45.cast (Variant::type_duration); - t.ok (v45.type () == Variant::type_duration, "cast date --> duration"); - t.ok (v45.get_duration () <= 1234567890 - now, "cast date --> duration"); - // Assuming this unit test takes less than 10 min to execute - t.ok (v45.get_duration () > 1234567890 - now - 600, "cast date --> duration"); - - // Variant::type_duration --> * - Variant v50 ((time_t) 12345, Variant::type_duration); - v50.cast (Variant::type_boolean); - t.ok (v50.type () == Variant::type_boolean, "cast duration --> boolean"); - t.ok (v50.get_bool () == true, "cast duration --> boolean"); - - Variant v51 ((time_t) 12345, Variant::type_duration); - v51.cast (Variant::type_integer); - t.ok (v51.type () == Variant::type_integer, "cast duration --> integer"); - t.ok (v51.get_integer () == 12345, "cast duration --> integer"); - - Variant v52 ((time_t) 12345, Variant::type_duration); - v52.cast (Variant::type_real); - t.ok (v52.type () == Variant::type_real, "cast duration --> real"); - t.is (v52.get_real (), 12345.0, EPSILON, "cast duration --> real"); - - Variant v53 ((time_t) 12345, Variant::type_duration); - v53.cast (Variant::type_string); - t.ok (v53.type () == Variant::type_string, "cast duration --> string"); - t.is (v53.get_string (), "PT3H25M45S", "cast duration --> string"); - - Variant v54 ((time_t) 12345, Variant::type_duration); - v54.cast (Variant::type_date); - t.ok (v54.type () == Variant::type_date, "cast duration --> date"); - t.ok (v54.get_date () >= 12345 + now, "cast duration --> duration"); - t.ok (v54.get_date () < 12345 + now + 600, "cast duration --> duration"); - - Variant v55 ((time_t) 12345, Variant::type_duration); - v55.cast (Variant::type_duration); - t.ok (v55.type () == Variant::type_duration, "cast duration --> duration"); - t.is (v55.get_duration (), 12345, "cast duration --> duration"); - } - - catch (const std::string& e) - { - t.diag (e); - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_cast_test.cpp b/test/variant_cast_test.cpp new file mode 100644 index 000000000..327881dc0 --- /dev/null +++ b/test/variant_cast_test.cpp @@ -0,0 +1,259 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(81); + + time_t now = time(nullptr); + + try { + // Variant::type_boolean --> * + Variant v00(true); + v00.cast(Variant::type_boolean); + t.ok(v00.type() == Variant::type_boolean, "cast boolean --> boolean"); + t.ok(v00.get_bool() == true, "cast boolean --> boolean"); + + Variant v01(true); + v01.cast(Variant::type_integer); + t.ok(v01.type() == Variant::type_integer, "cast boolean --> integer"); + t.ok(v01.get_integer() == 1, "cast boolean --> integer"); + + Variant v02(true); + v02.cast(Variant::type_real); + t.ok(v02.type() == Variant::type_real, "cast boolean --> real"); + t.is(v02.get_real(), 1.0, EPSILON, "cast boolean --> real"); + + Variant v03(true); + v03.cast(Variant::type_string); + t.ok(v03.type() == Variant::type_string, "cast boolean --> string"); + t.ok(v03.get_string() == "true", "cast boolean --> string"); + + Variant v04(true); + v04.cast(Variant::type_date); + t.ok(v04.type() == Variant::type_date, "cast boolean --> date"); + t.is(v04.get_date(), 1, "cast boolean --> date"); + + Variant v05(true); + v05.cast(Variant::type_duration); + t.ok(v05.type() == Variant::type_duration, "cast boolean --> duration"); + t.is(v05.get_duration(), 1, "cast boolean --> duration"); + + // Variant::type_integer --> * + Variant v10(42); + v10.cast(Variant::type_boolean); + t.ok(v10.type() == Variant::type_boolean, "cast integer --> boolean"); + t.ok(v10.get_bool() == true, "cast integer --> boolean"); + + Variant v11(42); + v11.cast(Variant::type_integer); + t.ok(v11.type() == Variant::type_integer, "cast integer --> integer"); + t.ok(v11.get_integer() == 42, "cast integer --> integer"); + + Variant v12(42); + v12.cast(Variant::type_real); + t.ok(v12.type() == Variant::type_real, "cast integer --> real"); + t.is(v12.get_real(), 42.0, EPSILON, "cast integer --> real"); + + Variant v13(42); + v13.cast(Variant::type_string); + t.is(v13.type(), Variant::type_string, "cast integer --> string"); + t.is(v13.get_string(), "42", "cast integer --> string"); + + Variant v14(42); + v14.cast(Variant::type_date); + t.ok(v14.type() == Variant::type_date, "cast integer --> date"); + t.ok(v14.get_date() == 42, "cast integer --> date"); + + Variant v15(42); + v15.cast(Variant::type_duration); + t.is(v15.type(), Variant::type_duration, "cast integer --> duration"); + t.is(v15.get_duration(), 42, "cast integer --> duration"); + + // Variant::type_real --> * + Variant v20(3.14); + v20.cast(Variant::type_boolean); + t.ok(v20.type() == Variant::type_boolean, "cast real --> boolean"); + t.ok(v20.get_bool() == true, "cast real --> boolean"); + + Variant v21(3.14); + v21.cast(Variant::type_integer); + t.ok(v21.type() == Variant::type_integer, "cast real --> integer"); + t.ok(v21.get_integer() == 3, "cast real --> integer"); + + Variant v22(3.14); + v22.cast(Variant::type_real); + t.ok(v22.type() == Variant::type_real, "cast real --> real"); + t.is(v22.get_real(), 3.14, EPSILON, "cast real --> real"); + + Variant v23(3.14); + v23.cast(Variant::type_string); + t.ok(v23.type() == Variant::type_string, "cast real --> string"); + t.ok(v23.get_string() == "3.14", "cast real --> string"); + + Variant v24(3.14); + v24.cast(Variant::type_date); + t.ok(v24.type() == Variant::type_date, "cast real --> date"); + t.ok(v24.get_date() == 3, "cast real --> date"); + + Variant v25(3.14); + v25.cast(Variant::type_duration); + t.ok(v25.type() == Variant::type_duration, "cast real --> duration"); + t.ok(v25.get_duration() == 3, "cast real --> duration"); + + // Variant::type_string --> * + Variant v30("foo"); + v30.cast(Variant::type_boolean); + t.ok(v30.type() == Variant::type_boolean, "cast string --> boolean"); + t.ok(v30.get_bool() == true, "cast string --> boolean"); + + Variant v31("42"); + v31.cast(Variant::type_integer); + t.ok(v31.type() == Variant::type_integer, "cast string --> integer"); + t.ok(v31.get_integer() == 42, "cast string --> integer"); + + Variant v31h("0x20"); + v31h.cast(Variant::type_integer); + t.ok(v31h.type() == Variant::type_integer, "cast string(hex) --> integer"); + t.ok(v31h.get_integer() == 32, "cast string(hex) --> integer"); + + Variant v32("3.14"); + v32.cast(Variant::type_real); + t.ok(v32.type() == Variant::type_real, "cast string --> real"); + t.is(v32.get_real(), 3.14, EPSILON, "cast string --> real"); + + Variant v33("foo"); + v33.cast(Variant::type_string); + t.ok(v33.type() == Variant::type_string, "cast string --> string"); + t.ok(v33.get_string() == "foo", "cast string --> string"); + + Variant v34("2013-12-07T16:33:00-05:00"); + v34.cast(Variant::type_date); + t.ok(v34.type() == Variant::type_date, "cast string --> date"); + t.ok(v34.get_date() == 1386451980, "cast string --> date"); + + Variant v35("42 days"); + v35.cast(Variant::type_duration); + t.ok(v35.type() == Variant::type_duration, "cast string --> duration"); + t.is(v35.get_duration(), 3628800, "cast string --> duration"); + + Variant v35b("P42D"); + v35b.cast(Variant::type_duration); + t.ok(v35b.type() == Variant::type_duration, "cast string --> duration"); + t.is(v35b.get_duration(), 3628800, "cast string --> duration"); + + // Variant::type_date --> * + Variant v40((time_t)1234567890, Variant::type_date); + v40.cast(Variant::type_boolean); + t.ok(v40.type() == Variant::type_boolean, "cast date --> boolean"); + t.ok(v40.get_bool() == true, "cast date --> boolean"); + + Variant v41((time_t)1234567890, Variant::type_date); + v41.cast(Variant::type_integer); + t.ok(v41.type() == Variant::type_integer, "cast date --> integer"); + t.ok(v41.get_integer() == 1234567890, "cast date --> integer"); + + Variant v42((time_t)1234567890, Variant::type_date); + v42.cast(Variant::type_real); + t.ok(v42.type() == Variant::type_real, "cast date --> real"); + t.is(v42.get_real(), 1234567890.0, EPSILON, "cast date --> real"); + + // YYYY-MM-DDThh:mm:ss + // ^ ^ ^ ^ ^ + Variant v43((time_t)1234567890, Variant::type_date); + v43.cast(Variant::type_string); + t.ok(v43.type() == Variant::type_string, "cast date --> string"); + std::string s = v43.get_string(); + t.is((int)s[4], (int)'-', "cast date --> string"); + t.is((int)s[7], (int)'-', "cast date --> string"); + t.is((int)s[10], (int)'T', "cast date --> string"); + t.is((int)s[13], (int)':', "cast date --> string"); + t.is((int)s[16], (int)':', "cast date --> string"); + t.is((int)s.length(), 19, "cast date --> string"); + + Variant v44((time_t)1234567890, Variant::type_date); + v44.cast(Variant::type_date); + t.ok(v44.type() == Variant::type_date, "cast date --> date"); + t.ok(v44.get_date() == 1234567890, "cast date --> date"); + + Variant v45((time_t)1234567890, Variant::type_date); + v45.cast(Variant::type_duration); + t.ok(v45.type() == Variant::type_duration, "cast date --> duration"); + t.ok(v45.get_duration() <= 1234567890 - now, "cast date --> duration"); + // Assuming this unit test takes less than 10 min to execute + t.ok(v45.get_duration() > 1234567890 - now - 600, "cast date --> duration"); + + // Variant::type_duration --> * + Variant v50((time_t)12345, Variant::type_duration); + v50.cast(Variant::type_boolean); + t.ok(v50.type() == Variant::type_boolean, "cast duration --> boolean"); + t.ok(v50.get_bool() == true, "cast duration --> boolean"); + + Variant v51((time_t)12345, Variant::type_duration); + v51.cast(Variant::type_integer); + t.ok(v51.type() == Variant::type_integer, "cast duration --> integer"); + t.ok(v51.get_integer() == 12345, "cast duration --> integer"); + + Variant v52((time_t)12345, Variant::type_duration); + v52.cast(Variant::type_real); + t.ok(v52.type() == Variant::type_real, "cast duration --> real"); + t.is(v52.get_real(), 12345.0, EPSILON, "cast duration --> real"); + + Variant v53((time_t)12345, Variant::type_duration); + v53.cast(Variant::type_string); + t.ok(v53.type() == Variant::type_string, "cast duration --> string"); + t.is(v53.get_string(), "PT3H25M45S", "cast duration --> string"); + + Variant v54((time_t)12345, Variant::type_duration); + v54.cast(Variant::type_date); + t.ok(v54.type() == Variant::type_date, "cast duration --> date"); + t.ok(v54.get_date() >= 12345 + now, "cast duration --> duration"); + t.ok(v54.get_date() < 12345 + now + 600, "cast duration --> duration"); + + Variant v55((time_t)12345, Variant::type_duration); + v55.cast(Variant::type_duration); + t.ok(v55.type() == Variant::type_duration, "cast duration --> duration"); + t.is(v55.get_duration(), 12345, "cast duration --> duration"); + } + + catch (const std::string& e) { + t.diag(e); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_divide.t.cpp b/test/variant_divide.t.cpp deleted file mode 100644 index 70a895a4a..000000000 --- a/test/variant_divide.t.cpp +++ /dev/null @@ -1,201 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.0001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (44); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean / boolean -> ERROR - try {Variant v00 = v0 / v0; t.fail ("true / true --> error");} - catch (...) {t.pass ("true / true --> error");} - - // boolean / integer -> ERROR - try {Variant v01 = v0 / v1; t.fail ("true / 42 --> error");} - catch (...) {t.pass ("true / 42 --> error");} - - // boolean / real -> ERROR - try {Variant v02 = v0 / v2; t.fail ("true / 3.14 --> error");} - catch (...) {t.pass ("true / 3.14 --> error");} - - // boolean / string -> ERROR - try {Variant v03 = v0 / v3; t.fail ("true / foo --> error");} - catch (...) {t.pass ("true / foo --> error");} - - // boolean / date -> ERROR - try {Variant v04 = v0 / v4; t.fail ("true / 1234567890 --> error");} - catch (...) {t.pass ("true / 1234567890 --> error");} - - // boolean / duration -> ERROR - try {Variant v05 = v0 / v5; t.fail ("true / 1200 --> error");} - catch (...) {t.pass ("true / 1200 --> error");} - - // integer / boolean -> ERROR - try {Variant v10 = v1 / v0; t.fail ("42 / true --> error");} - catch (...) {t.pass ("42 / true --> error");} - - // integer / integer -> integer - Variant v11 = v1 / v1; - t.is (v11.type (), Variant::type_integer, "42 / 42 --> integer"); - t.is (v11.get_integer (), 1, "42 / 42 --> 1"); - - // integer / real -> real - Variant v12 = v1 / v2; - t.is (v12.type (), Variant::type_real, "42 / 3.14 --> real"); - t.is (v12.get_real (), 13.3757, EPSILON, "42 / 3.14 --> 13.3757"); - - // integer / string -> ERROR - try {Variant v13 = v1 / v3; t.fail ("42 / foo --> error");} - catch (...) {t.pass ("42 / foo --> error");} - - // integer / date -> ERROR - try {Variant v14 = v1 / v4; t.fail ("42 / 1234567890 --> error");} - catch (...) {t.pass ("42 / 1234567890 --> error");} - - // integer / duration -> duration - Variant v15 = v1 / v5; - t.is (v15.type (), Variant::type_duration, "42 / 1200 --> duration"); - t.is (v15.get_duration (), 0, "42 / 1200 --> 0"); - - // real / boolean -> ERROR - try {Variant v20 = v2 / v0; t.fail ("3.14 / true --> error");} - catch (...) {t.pass ("3.14 / true --> error");} - - // real / integer -> real - Variant v21 = v2 / v1; - t.is (v21.type (), Variant::type_real, "3.14 / 42 --> real"); - t.is (v21.get_real (), 0.0747, EPSILON, "3.14 / 42 --> 0.0747"); - - // real / real -> real - Variant v22 = v2 / v2; - t.is (v22.type (), Variant::type_real, "3.14 / 3.14 --> real"); - t.is (v22.get_real (), 1.0, EPSILON, "3.14 / 3.14 --> 1.0"); - - // real / string -> error - try {Variant v23 = v2 / v3; t.fail ("3.14 / foo --> error");} - catch (...) {t.pass ("3.14 / foo --> error");} - - // real / date -> error - try {Variant v24 = v2 / v4; t.fail ("3.14 / 1234567890 --> error");} - catch (...) {t.pass ("3.14 / 1234567890 --> error");} - - // real / duration -> duration - Variant v25 = v2 / v5; - t.is (v25.type (), Variant::type_duration, "3.14 / 1200 --> duration"); - t.is (v25.get_duration (), 0, "3.14 / 1200 --> 0"); - - // string / boolean -> ERROR - try {Variant v30 = v3 / v0; t.fail ("foo / true --> error");} - catch (...) {t.pass ("foo / true --> error");} - - // string / integer -> ERROR - try {Variant v31 = v3 / v1; t.fail ("foo / 42 --> error");} - catch (...) {t.pass ("foo / 42 --> error");} - - // string / real -> ERROR - try {Variant v32 = v3 / v2; t.fail ("foo / 3.14 --> error");} - catch (...) {t.pass ("foo / 3.14 --> error");} - - // string / string -> ERROR - try {Variant v33 = v3 / v3; t.fail ("foo / foo --> error");} - catch (...) {t.pass ("foo / foo --> error");} - - // string / date -> ERROR - try {Variant v34 = v3 / v4; t.fail ("foo / 1234567890 --> error");} - catch (...) {t.pass ("foo / 1234567890 --> error");} - - // string / duration -> ERROR - try {Variant v35 = v3 / v5; t.fail ("foo / 1200 --> error");} - catch (...) {t.pass ("foo / 1200 --> error");} - - // date / boolean -> ERROR - try {Variant v40 = v4 / v0; t.fail ("1234567890 / true --> error");} - catch (...) {t.pass ("1234567890 / true --> error");} - - // date / integer -> ERROR - try {Variant v41 = v4 / v1; t.fail ("1234567890 / 42 --> error");} - catch (...) {t.pass ("1234567890 / 42 --> error");} - - // date / real -> ERROR - try {Variant v42 = v4 / v2; t.fail ("1234567890 / 3.14 --> error");} - catch (...) {t.pass ("1234567890 / 3.14 --> error");} - - // date / string -> ERROR - try {Variant v43 = v4 / v3; t.fail ("1234567890 / foo --> error");} - catch (...) {t.pass ("1234567890 / foo --> error");} - - // date / date -> ERROR - try {Variant v44 = v4 / v4; t.fail ("1234567890 / 1234567890 --> error");} - catch (...) {t.pass ("1234567890 / 1234567890 --> error");} - - // date / duration -> ERROR - try {Variant v45 = v4 / v5; t.fail ("1234567890 / 1200 --> error");} - catch (...) {t.pass ("1234567890 / 1200 --> error");} - - // duration / boolean -> ERROR - try {Variant v50 = v5 / v0; t.fail ("1200 / true --> error");} - catch (...) {t.pass ("1200 / true --> error");} - - // duration / integer -> duration - Variant v51 = v5 / v1; - t.is (v51.type (), Variant::type_duration, "1200 / 42 --> duration"); - t.is (v51.get_duration (), 28, "1200 / 42 --> 28"); - - // duration / real -> duration - Variant v52 = v5 / v2; - t.is (v52.type (), Variant::type_duration, "1200 / 3.14 --> duration"); - t.is (v52.get_duration (), 382, "1200 / 3.14 --> 382"); - - // duration / string -> string - try {Variant v53 = v5 / v3; t.fail ("1200 / foo --> error");} - catch (...) {t.pass ("1200 / foo --> error");} - - // duration / date -> date - try {Variant v54 = v5 / v4; t.fail ("1200 / 1234567890 --> error");} - catch (...) {t.pass ("1200 / 1234567890 --> error");} - - // duration / duration -> duration - try {Variant v55 = v5 / v5; t.fail ("1200 / 1200 --> error");} - catch (...) {t.pass ("1200 / 1200 --> error");} - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_divide_test.cpp b/test/variant_divide_test.cpp new file mode 100644 index 000000000..b1fc973d3 --- /dev/null +++ b/test/variant_divide_test.cpp @@ -0,0 +1,312 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.0001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(44); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean / boolean -> ERROR + try { + Variant v00 = v0 / v0; + t.fail("true / true --> error"); + } catch (...) { + t.pass("true / true --> error"); + } + + // boolean / integer -> ERROR + try { + Variant v01 = v0 / v1; + t.fail("true / 42 --> error"); + } catch (...) { + t.pass("true / 42 --> error"); + } + + // boolean / real -> ERROR + try { + Variant v02 = v0 / v2; + t.fail("true / 3.14 --> error"); + } catch (...) { + t.pass("true / 3.14 --> error"); + } + + // boolean / string -> ERROR + try { + Variant v03 = v0 / v3; + t.fail("true / foo --> error"); + } catch (...) { + t.pass("true / foo --> error"); + } + + // boolean / date -> ERROR + try { + Variant v04 = v0 / v4; + t.fail("true / 1234567890 --> error"); + } catch (...) { + t.pass("true / 1234567890 --> error"); + } + + // boolean / duration -> ERROR + try { + Variant v05 = v0 / v5; + t.fail("true / 1200 --> error"); + } catch (...) { + t.pass("true / 1200 --> error"); + } + + // integer / boolean -> ERROR + try { + Variant v10 = v1 / v0; + t.fail("42 / true --> error"); + } catch (...) { + t.pass("42 / true --> error"); + } + + // integer / integer -> integer + Variant v11 = v1 / v1; + t.is(v11.type(), Variant::type_integer, "42 / 42 --> integer"); + t.is(v11.get_integer(), 1, "42 / 42 --> 1"); + + // integer / real -> real + Variant v12 = v1 / v2; + t.is(v12.type(), Variant::type_real, "42 / 3.14 --> real"); + t.is(v12.get_real(), 13.3757, EPSILON, "42 / 3.14 --> 13.3757"); + + // integer / string -> ERROR + try { + Variant v13 = v1 / v3; + t.fail("42 / foo --> error"); + } catch (...) { + t.pass("42 / foo --> error"); + } + + // integer / date -> ERROR + try { + Variant v14 = v1 / v4; + t.fail("42 / 1234567890 --> error"); + } catch (...) { + t.pass("42 / 1234567890 --> error"); + } + + // integer / duration -> duration + Variant v15 = v1 / v5; + t.is(v15.type(), Variant::type_duration, "42 / 1200 --> duration"); + t.is(v15.get_duration(), 0, "42 / 1200 --> 0"); + + // real / boolean -> ERROR + try { + Variant v20 = v2 / v0; + t.fail("3.14 / true --> error"); + } catch (...) { + t.pass("3.14 / true --> error"); + } + + // real / integer -> real + Variant v21 = v2 / v1; + t.is(v21.type(), Variant::type_real, "3.14 / 42 --> real"); + t.is(v21.get_real(), 0.0747, EPSILON, "3.14 / 42 --> 0.0747"); + + // real / real -> real + Variant v22 = v2 / v2; + t.is(v22.type(), Variant::type_real, "3.14 / 3.14 --> real"); + t.is(v22.get_real(), 1.0, EPSILON, "3.14 / 3.14 --> 1.0"); + + // real / string -> error + try { + Variant v23 = v2 / v3; + t.fail("3.14 / foo --> error"); + } catch (...) { + t.pass("3.14 / foo --> error"); + } + + // real / date -> error + try { + Variant v24 = v2 / v4; + t.fail("3.14 / 1234567890 --> error"); + } catch (...) { + t.pass("3.14 / 1234567890 --> error"); + } + + // real / duration -> duration + Variant v25 = v2 / v5; + t.is(v25.type(), Variant::type_duration, "3.14 / 1200 --> duration"); + t.is(v25.get_duration(), 0, "3.14 / 1200 --> 0"); + + // string / boolean -> ERROR + try { + Variant v30 = v3 / v0; + t.fail("foo / true --> error"); + } catch (...) { + t.pass("foo / true --> error"); + } + + // string / integer -> ERROR + try { + Variant v31 = v3 / v1; + t.fail("foo / 42 --> error"); + } catch (...) { + t.pass("foo / 42 --> error"); + } + + // string / real -> ERROR + try { + Variant v32 = v3 / v2; + t.fail("foo / 3.14 --> error"); + } catch (...) { + t.pass("foo / 3.14 --> error"); + } + + // string / string -> ERROR + try { + Variant v33 = v3 / v3; + t.fail("foo / foo --> error"); + } catch (...) { + t.pass("foo / foo --> error"); + } + + // string / date -> ERROR + try { + Variant v34 = v3 / v4; + t.fail("foo / 1234567890 --> error"); + } catch (...) { + t.pass("foo / 1234567890 --> error"); + } + + // string / duration -> ERROR + try { + Variant v35 = v3 / v5; + t.fail("foo / 1200 --> error"); + } catch (...) { + t.pass("foo / 1200 --> error"); + } + + // date / boolean -> ERROR + try { + Variant v40 = v4 / v0; + t.fail("1234567890 / true --> error"); + } catch (...) { + t.pass("1234567890 / true --> error"); + } + + // date / integer -> ERROR + try { + Variant v41 = v4 / v1; + t.fail("1234567890 / 42 --> error"); + } catch (...) { + t.pass("1234567890 / 42 --> error"); + } + + // date / real -> ERROR + try { + Variant v42 = v4 / v2; + t.fail("1234567890 / 3.14 --> error"); + } catch (...) { + t.pass("1234567890 / 3.14 --> error"); + } + + // date / string -> ERROR + try { + Variant v43 = v4 / v3; + t.fail("1234567890 / foo --> error"); + } catch (...) { + t.pass("1234567890 / foo --> error"); + } + + // date / date -> ERROR + try { + Variant v44 = v4 / v4; + t.fail("1234567890 / 1234567890 --> error"); + } catch (...) { + t.pass("1234567890 / 1234567890 --> error"); + } + + // date / duration -> ERROR + try { + Variant v45 = v4 / v5; + t.fail("1234567890 / 1200 --> error"); + } catch (...) { + t.pass("1234567890 / 1200 --> error"); + } + + // duration / boolean -> ERROR + try { + Variant v50 = v5 / v0; + t.fail("1200 / true --> error"); + } catch (...) { + t.pass("1200 / true --> error"); + } + + // duration / integer -> duration + Variant v51 = v5 / v1; + t.is(v51.type(), Variant::type_duration, "1200 / 42 --> duration"); + t.is(v51.get_duration(), 28, "1200 / 42 --> 28"); + + // duration / real -> duration + Variant v52 = v5 / v2; + t.is(v52.type(), Variant::type_duration, "1200 / 3.14 --> duration"); + t.is(v52.get_duration(), 382, "1200 / 3.14 --> 382"); + + // duration / string -> string + try { + Variant v53 = v5 / v3; + t.fail("1200 / foo --> error"); + } catch (...) { + t.pass("1200 / foo --> error"); + } + + // duration / date -> date + try { + Variant v54 = v5 / v4; + t.fail("1200 / 1234567890 --> error"); + } catch (...) { + t.pass("1200 / 1234567890 --> error"); + } + + // duration / duration -> duration + Variant v55 = v5 / v5; + t.is(v55.type(), Variant::type_real, "1200 / 1200 --> real"); + t.is(v55.get_real(), 1.0, "1200 / 1200 --> 1.0"); + t.is((v5 / v52).get_real(), 3.14136, EPSILON, "1200 / 382 --> 3.14136"); + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_equal.t.cpp b/test/variant_equal.t.cpp deleted file mode 100644 index b89d2e782..000000000 --- a/test/variant_equal.t.cpp +++ /dev/null @@ -1,191 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 == v0; - t.is (v00.type (), Variant::type_boolean, "true == true --> boolean"); - t.is (v00.get_bool (), true, "true == true --> true"); - - Variant v01 = v0 == v1; - t.is (v01.type (), Variant::type_boolean, "true == 42 --> boolean"); - t.is (v01.get_bool (), false, "true == 42 --> false"); - - Variant v02 = v0 == v2; - t.is (v02.type (), Variant::type_boolean, "true == 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true == 3.14 --> false"); - - Variant v03 = v0 == v3; - t.is (v03.type (), Variant::type_boolean, "true == 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true == 'foo' --> false"); - - Variant v04 = v0 == v4; - t.is (v04.type (), Variant::type_boolean, "true == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true == 1234567890 --> false"); - - Variant v05 = v0 == v5; - t.is (v05.type (), Variant::type_boolean, "true == 1200 --> boolean"); - t.is (v05.get_bool (), false, "true == 1200 --> false"); - - Variant v10 = v1 == v0; - t.is (v10.type (), Variant::type_boolean, "42 == true --> boolean"); - t.is (v10.get_bool (), false, "42 == true --> false"); - - Variant v11 = v1 == v1; - t.is (v11.type (), Variant::type_boolean, "42 == 42 --> boolean"); - t.is (v11.get_bool (), true, "42 == 42 --> true"); - - Variant v12 = v1 == v2; - t.is (v12.type (), Variant::type_boolean, "42 == 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 == 3.14 --> false"); - - Variant v13 = v1 == v3; - t.is (v13.type (), Variant::type_boolean, "42 == 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 == 'foo' --> false"); - - Variant v14 = v1 == v4; - t.is (v04.type (), Variant::type_boolean, "42 == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 == 1234567890 --> false"); - - Variant v15 = v1 == v5; - t.is (v15.type (), Variant::type_boolean, "42 == 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 == 1200 --> false"); - - Variant v20 = v2 == v0; - t.is (v20.type (), Variant::type_boolean, "3.14 == true --> boolean"); - t.is (v20.get_bool (), false, "3.14 == true --> false"); - - Variant v21 = v2 == v1; - t.is (v21.type (), Variant::type_boolean, "3.14 == 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 == 42 --> false"); - - Variant v22 = v2 == v2; - t.is (v22.type (), Variant::type_boolean, "3.14 == 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 == 3.14 --> true"); - - Variant v23 = v2 == v3; - t.is (v23.type (), Variant::type_boolean, "3.14 == 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 == 'foo' --> false"); - - Variant v24 = v2 == v4; - t.is (v24.type (), Variant::type_boolean, "3.14 == 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 == 1234567890 --> false"); - - Variant v25 = v2 == v5; - t.is (v25.type (), Variant::type_boolean, "3.14 == 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 == 1200 --> false"); - - Variant v30 = v3 == v0; - t.is (v30.type (), Variant::type_boolean, "'foo' == true --> boolean"); - t.is (v30.get_bool (), false, "'foo' == true --> false"); - - Variant v31 = v3 == v1; - t.is (v31.type (), Variant::type_boolean, "'foo' == 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' == 42 --> false"); - - Variant v32 = v3 == v2; - t.is (v32.type (), Variant::type_boolean, "'foo' == 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' == 3.14 --> false"); - - Variant v33 = v3 == v3; - t.is (v33.type (), Variant::type_boolean, "'foo' == 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' == 'foo' --> true"); - - Variant v34 = v3 == v4; - t.is (v34.type (), Variant::type_boolean, "'foo' == 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' == 1234567890 --> false"); - - Variant v35 = v3 == v5; - t.is (v35.type (), Variant::type_boolean, "'foo' == 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' == 1200 --> false"); - - Variant v40 = v4 == v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 == true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 == true --> false"); - - Variant v41 = v4 == v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 == 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 == 42 --> false"); - - Variant v42 = v4 == v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 == 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 == 3.14 --> false"); - - Variant v43 = v4 == v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 == 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 == 'foo' --> false"); - - Variant v44 = v4 == v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 == 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 == 1234567890 --> true"); - - Variant v45 = v4 == v5; - t.is (v45.type (), Variant::type_boolean, "1234567890 == 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 == 1200 --> false"); - - Variant v50 = v5 == v0; - t.is (v50.type (), Variant::type_boolean, "1200 == true --> boolean"); - t.is (v50.get_bool (), false, "1200 == true --> false"); - - Variant v51 = v5 == v1; - t.is (v51.type (), Variant::type_boolean, "1200 == 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 == 42 --> false"); - - Variant v52 = v5 == v2; - t.is (v52.type (), Variant::type_boolean, "1200 == 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 == 3.14 --> false"); - - Variant v53 = v5 == v3; - t.is (v53.type (), Variant::type_boolean, "1200 == 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 == 'foo' --> false"); - - Variant v54 = v5 == v4; - t.is (v04.type (), Variant::type_boolean, "1200 == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "1200 == 1234567890 --> false"); - - Variant v55 = v5 == v5; - t.is (v55.type (), Variant::type_boolean, "1200 == 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 == 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_equal_test.cpp b/test/variant_equal_test.cpp new file mode 100644 index 000000000..2383c6e96 --- /dev/null +++ b/test/variant_equal_test.cpp @@ -0,0 +1,193 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 == v0; + t.is(v00.type(), Variant::type_boolean, "true == true --> boolean"); + t.is(v00.get_bool(), true, "true == true --> true"); + + Variant v01 = v0 == v1; + t.is(v01.type(), Variant::type_boolean, "true == 42 --> boolean"); + t.is(v01.get_bool(), false, "true == 42 --> false"); + + Variant v02 = v0 == v2; + t.is(v02.type(), Variant::type_boolean, "true == 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true == 3.14 --> false"); + + Variant v03 = v0 == v3; + t.is(v03.type(), Variant::type_boolean, "true == 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true == 'foo' --> false"); + + Variant v04 = v0 == v4; + t.is(v04.type(), Variant::type_boolean, "true == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true == 1234567890 --> false"); + + Variant v05 = v0 == v5; + t.is(v05.type(), Variant::type_boolean, "true == 1200 --> boolean"); + t.is(v05.get_bool(), false, "true == 1200 --> false"); + + Variant v10 = v1 == v0; + t.is(v10.type(), Variant::type_boolean, "42 == true --> boolean"); + t.is(v10.get_bool(), false, "42 == true --> false"); + + Variant v11 = v1 == v1; + t.is(v11.type(), Variant::type_boolean, "42 == 42 --> boolean"); + t.is(v11.get_bool(), true, "42 == 42 --> true"); + + Variant v12 = v1 == v2; + t.is(v12.type(), Variant::type_boolean, "42 == 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 == 3.14 --> false"); + + Variant v13 = v1 == v3; + t.is(v13.type(), Variant::type_boolean, "42 == 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 == 'foo' --> false"); + + Variant v14 = v1 == v4; + t.is(v04.type(), Variant::type_boolean, "42 == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 == 1234567890 --> false"); + + Variant v15 = v1 == v5; + t.is(v15.type(), Variant::type_boolean, "42 == 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 == 1200 --> false"); + + Variant v20 = v2 == v0; + t.is(v20.type(), Variant::type_boolean, "3.14 == true --> boolean"); + t.is(v20.get_bool(), false, "3.14 == true --> false"); + + Variant v21 = v2 == v1; + t.is(v21.type(), Variant::type_boolean, "3.14 == 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 == 42 --> false"); + + Variant v22 = v2 == v2; + t.is(v22.type(), Variant::type_boolean, "3.14 == 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 == 3.14 --> true"); + + Variant v23 = v2 == v3; + t.is(v23.type(), Variant::type_boolean, "3.14 == 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 == 'foo' --> false"); + + Variant v24 = v2 == v4; + t.is(v24.type(), Variant::type_boolean, "3.14 == 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 == 1234567890 --> false"); + + Variant v25 = v2 == v5; + t.is(v25.type(), Variant::type_boolean, "3.14 == 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 == 1200 --> false"); + + Variant v30 = v3 == v0; + t.is(v30.type(), Variant::type_boolean, "'foo' == true --> boolean"); + t.is(v30.get_bool(), false, "'foo' == true --> false"); + + Variant v31 = v3 == v1; + t.is(v31.type(), Variant::type_boolean, "'foo' == 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' == 42 --> false"); + + Variant v32 = v3 == v2; + t.is(v32.type(), Variant::type_boolean, "'foo' == 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' == 3.14 --> false"); + + Variant v33 = v3 == v3; + t.is(v33.type(), Variant::type_boolean, "'foo' == 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' == 'foo' --> true"); + + Variant v34 = v3 == v4; + t.is(v34.type(), Variant::type_boolean, "'foo' == 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' == 1234567890 --> false"); + + Variant v35 = v3 == v5; + t.is(v35.type(), Variant::type_boolean, "'foo' == 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' == 1200 --> false"); + + Variant v40 = v4 == v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 == true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 == true --> false"); + + Variant v41 = v4 == v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 == 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 == 42 --> false"); + + Variant v42 = v4 == v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 == 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 == 3.14 --> false"); + + Variant v43 = v4 == v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 == 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 == 'foo' --> false"); + + Variant v44 = v4 == v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 == 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 == 1234567890 --> true"); + + Variant v45 = v4 == v5; + t.is(v45.type(), Variant::type_boolean, "1234567890 == 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 == 1200 --> false"); + + Variant v50 = v5 == v0; + t.is(v50.type(), Variant::type_boolean, "1200 == true --> boolean"); + t.is(v50.get_bool(), false, "1200 == true --> false"); + + Variant v51 = v5 == v1; + t.is(v51.type(), Variant::type_boolean, "1200 == 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 == 42 --> false"); + + Variant v52 = v5 == v2; + t.is(v52.type(), Variant::type_boolean, "1200 == 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 == 3.14 --> false"); + + Variant v53 = v5 == v3; + t.is(v53.type(), Variant::type_boolean, "1200 == 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 == 'foo' --> false"); + + Variant v54 = v5 == v4; + t.is(v04.type(), Variant::type_boolean, "1200 == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "1200 == 1234567890 --> false"); + + Variant v55 = v5 == v5; + t.is(v55.type(), Variant::type_boolean, "1200 == 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 == 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_exp.t.cpp b/test/variant_exp.t.cpp deleted file mode 100644 index b8c9d227c..000000000 --- a/test/variant_exp.t.cpp +++ /dev/null @@ -1,193 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (38); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean ^ boolean -> ERROR - try {Variant v00 = v0 ^ v0; t.fail ("true ^ true --> error");} - catch (...) {t.pass ("true ^ true --> error");} - - // boolean ^ integer -> ERROR - try {Variant v01 = v0 ^ v1; t.fail ("true ^ 42 --> error");} - catch (...) {t.pass ("true ^ 42 --> error");} - - // boolean ^ real -> ERROR - try {Variant v02 = v0 ^ v2; t.fail ("true ^ 3.14 --> error");} - catch (...) {t.pass ("true ^ 3.14 --> error");} - - // boolean ^ string -> ERROR - try {Variant v03 = v0 ^ v3; t.fail ("true ^ foo --> error");} - catch (...) {t.pass ("true ^ foo --> error");} - - // boolean ^ date -> ERROR - try {Variant v04 = v0 ^ v4; t.fail ("true ^ 1234567890 --> error");} - catch (...) {t.pass ("true ^ 1234567890 --> error");} - - // boolean ^ duration -> ERROR - try {Variant v05 = v0 ^ v5; t.fail ("true ^ 1200 --> error");} - catch (...) {t.pass ("true ^ 1200 --> error");} - - // integer ^ boolean -> ERROR - try {Variant v10 = v1 ^ v0; t.fail ("42 ^ true --> error");} - catch (...) {t.pass ("42 ^ true --> error");} - - // integer ^ integer -> integer - Variant v11 = v1 ^ Variant (2); - t.is (v11.type (), Variant::type_integer, "42 ^ 2 --> integer"); - t.is (v11.get_integer (), 1764, "42 ^ 2 --> 1764"); - - // integer ^ real -> real - try {Variant v12 = v1 ^ v2; t.fail ("42 ^ 3.14 --> error");} - catch (...) {t.pass ("42 ^ 3.14 --> error");} - - // integer ^ string -> ERROR - try {Variant v13 = v1 ^ v3; t.fail ("42 ^ foo --> error");} - catch (...) {t.pass ("42 ^ foo --> error");} - - // integer ^ date -> ERROR - try {Variant v14 = v1 ^ v4; t.fail ("42 ^ 1234567890 --> error");} - catch (...) {t.pass ("42 ^ 1234567890 --> error");} - - // integer ^ duration -> ERROR - try {Variant v15 = v1 ^ v5; t.fail ("42 ^ 1200 --> error");} - catch (...) {t.pass ("42 ^ 1200 --> error");} - - // real ^ boolean -> ERROR - try {Variant v20 = v2 ^ v0; t.fail ("3.14 ^ true --> error");} - catch (...) {t.pass ("3.14 ^ true --> error");} - - // real ^ integer -> real - Variant v21 = v2 ^ Variant (2); - t.is (v21.type (), Variant::type_real, "3.14 ^ 2 --> real"); - t.is (v21.get_real (), 9.8596, 0.001, "3.14 ^ 2 --> 9.8596"); - - // real ^ real -> ERROR - try {Variant v22 = v2 ^ v2; t.fail ("3.14 ^ 3.14 --> error");} - catch (...) {t.pass ("3.14 ^ 3.14 --> error");} - - // real ^ string -> ERROR - try {Variant v23 = v2 ^ v3; t.fail ("3.14 ^ foo --> error");} - catch (...) {t.pass ("3.14 ^ foo --> error");} - - // real ^ date -> ERROR - try {Variant v24 = v2 ^ v4; t.fail ("3.14 ^ 1234567890 --> error");} - catch (...) {t.pass ("3.14 ^ 1234567890 --> error");} - - // real ^ duration -> ERROR - try {Variant v25 = v2 ^ v5; t.fail ("3.14 ^ 1200 --> error");} - catch (...) {t.pass ("3.14 ^ 1200 --> error");} - - // string ^ boolean -> ERROR - try {Variant v30 = v3 ^ v0; t.fail ("foo ^ true --> error");} - catch (...) {t.pass ("foo ^ true --> error");} - - // string ^ integer -> ERROR - try {Variant v31 = v3 ^ v1; t.fail ("foo ^ 42 --> error");} - catch (...) {t.pass ("foo ^ 42 --> error");} - - // string ^ real -> ERROR - try {Variant v32 = v3 ^ v2; t.fail ("foo ^ 3.14 --> error");} - catch (...) {t.pass ("foo ^ 3.14 --> error");} - - // string ^ string -> ERROR - try {Variant v33 = v3 ^ v3; t.fail ("foo ^ foo --> error");} - catch (...) {t.pass ("foo ^ foo --> error");} - - // string ^ date -> ERROR - try {Variant v34 = v3 ^ v4; t.fail ("foo ^ 1234567890 --> error");} - catch (...) {t.pass ("foo ^ 1234567890 --> error");} - - // string ^ duration -> ERROR - try {Variant v35 = v3 ^ v5; t.fail ("foo ^ 1200 --> error");} - catch (...) {t.pass ("foo ^ 1200 --> error");} - - // date ^ boolean -> ERROR - try {Variant v40 = v4 ^ v0; t.fail ("1234567890 ^ true --> error");} - catch (...) {t.pass ("1234567890 ^ true --> error");} - - // date ^ integer -> ERROR - try {Variant v41 = v4 ^ v1; t.fail ("1234567890 ^ 42 --> error");} - catch (...) {t.pass ("1234567890 ^ 42 --> error");} - - // date ^ real -> ERROR - try {Variant v42 = v4 ^ v2; t.fail ("1234567890 ^ 3.14 --> error");} - catch (...) {t.pass ("1234567890 ^ 3.14 --> error");} - - // date ^ string -> ERROR - try {Variant v43 = v4 ^ v3; t.fail ("1234567890 ^ foo --> error");} - catch (...) {t.pass ("1234567890 ^ foo --> error");} - - // date ^ date -> ERROR - try {Variant v44 = v4 ^ v4; t.fail ("1234567890 ^ 1234567890 --> error");} - catch (...) {t.pass ("1234567890 ^ 1234567890 --> error");} - - // date ^ duration -> ERROR - try {Variant v45 = v4 ^ v5; t.fail ("1234567890 ^ 1200 --> error");} - catch (...) {t.pass ("1234567890 ^ 1200 --> error");} - - // duration ^ boolean -> ERROR - try {Variant v50 = v5 ^ v0; t.fail ("1200 ^ true --> error");} - catch (...) {t.pass ("1200 ^ true --> error");} - - // duration ^ integer -> ERROR - try {Variant v51 = v5 ^ v1; t.fail ("1200 ^ 42 --> error");} - catch (...) {t.pass ("1200 ^ 42 --> error");} - - // duration ^ real -> ERROR - try {Variant v52 = v5 ^ v2; t.fail ("1200 ^ 3.14 --> error");} - catch (...) {t.pass ("1200 ^ 3.14 --> error");} - - // duration ^ string -> ERROR - try {Variant v53 = v5 ^ v3; t.fail ("1200 ^ foo --> error");} - catch (...) {t.pass ("1200 ^ foo --> error");} - - // duration ^ date -> ERROR - try {Variant v54 = v5 ^ v4; t.fail ("1200 ^ 1234567890 --> error");} - catch (...) {t.pass ("1200 ^ 1234567890 --> error");} - - // duration ^ duration -> ERROR - try {Variant v55 = v5 ^ v5; t.fail ("1200 ^ 1200 --> error");} - catch (...) {t.pass ("1200 ^ 1200 --> error");} - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_exp_test.cpp b/test/variant_exp_test.cpp new file mode 100644 index 000000000..00fd48121 --- /dev/null +++ b/test/variant_exp_test.cpp @@ -0,0 +1,331 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(38); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean ^ boolean -> ERROR + try { + Variant v00 = v0 ^ v0; + t.fail("true ^ true --> error"); + } catch (...) { + t.pass("true ^ true --> error"); + } + + // boolean ^ integer -> ERROR + try { + Variant v01 = v0 ^ v1; + t.fail("true ^ 42 --> error"); + } catch (...) { + t.pass("true ^ 42 --> error"); + } + + // boolean ^ real -> ERROR + try { + Variant v02 = v0 ^ v2; + t.fail("true ^ 3.14 --> error"); + } catch (...) { + t.pass("true ^ 3.14 --> error"); + } + + // boolean ^ string -> ERROR + try { + Variant v03 = v0 ^ v3; + t.fail("true ^ foo --> error"); + } catch (...) { + t.pass("true ^ foo --> error"); + } + + // boolean ^ date -> ERROR + try { + Variant v04 = v0 ^ v4; + t.fail("true ^ 1234567890 --> error"); + } catch (...) { + t.pass("true ^ 1234567890 --> error"); + } + + // boolean ^ duration -> ERROR + try { + Variant v05 = v0 ^ v5; + t.fail("true ^ 1200 --> error"); + } catch (...) { + t.pass("true ^ 1200 --> error"); + } + + // integer ^ boolean -> ERROR + try { + Variant v10 = v1 ^ v0; + t.fail("42 ^ true --> error"); + } catch (...) { + t.pass("42 ^ true --> error"); + } + + // integer ^ integer -> integer + Variant v11 = v1 ^ Variant(2); + t.is(v11.type(), Variant::type_integer, "42 ^ 2 --> integer"); + t.is(v11.get_integer(), 1764, "42 ^ 2 --> 1764"); + + // integer ^ real -> real + try { + Variant v12 = v1 ^ v2; + t.fail("42 ^ 3.14 --> error"); + } catch (...) { + t.pass("42 ^ 3.14 --> error"); + } + + // integer ^ string -> ERROR + try { + Variant v13 = v1 ^ v3; + t.fail("42 ^ foo --> error"); + } catch (...) { + t.pass("42 ^ foo --> error"); + } + + // integer ^ date -> ERROR + try { + Variant v14 = v1 ^ v4; + t.fail("42 ^ 1234567890 --> error"); + } catch (...) { + t.pass("42 ^ 1234567890 --> error"); + } + + // integer ^ duration -> ERROR + try { + Variant v15 = v1 ^ v5; + t.fail("42 ^ 1200 --> error"); + } catch (...) { + t.pass("42 ^ 1200 --> error"); + } + + // real ^ boolean -> ERROR + try { + Variant v20 = v2 ^ v0; + t.fail("3.14 ^ true --> error"); + } catch (...) { + t.pass("3.14 ^ true --> error"); + } + + // real ^ integer -> real + Variant v21 = v2 ^ Variant(2); + t.is(v21.type(), Variant::type_real, "3.14 ^ 2 --> real"); + t.is(v21.get_real(), 9.8596, 0.001, "3.14 ^ 2 --> 9.8596"); + + // real ^ real -> ERROR + try { + Variant v22 = v2 ^ v2; + t.fail("3.14 ^ 3.14 --> error"); + } catch (...) { + t.pass("3.14 ^ 3.14 --> error"); + } + + // real ^ string -> ERROR + try { + Variant v23 = v2 ^ v3; + t.fail("3.14 ^ foo --> error"); + } catch (...) { + t.pass("3.14 ^ foo --> error"); + } + + // real ^ date -> ERROR + try { + Variant v24 = v2 ^ v4; + t.fail("3.14 ^ 1234567890 --> error"); + } catch (...) { + t.pass("3.14 ^ 1234567890 --> error"); + } + + // real ^ duration -> ERROR + try { + Variant v25 = v2 ^ v5; + t.fail("3.14 ^ 1200 --> error"); + } catch (...) { + t.pass("3.14 ^ 1200 --> error"); + } + + // string ^ boolean -> ERROR + try { + Variant v30 = v3 ^ v0; + t.fail("foo ^ true --> error"); + } catch (...) { + t.pass("foo ^ true --> error"); + } + + // string ^ integer -> ERROR + try { + Variant v31 = v3 ^ v1; + t.fail("foo ^ 42 --> error"); + } catch (...) { + t.pass("foo ^ 42 --> error"); + } + + // string ^ real -> ERROR + try { + Variant v32 = v3 ^ v2; + t.fail("foo ^ 3.14 --> error"); + } catch (...) { + t.pass("foo ^ 3.14 --> error"); + } + + // string ^ string -> ERROR + try { + Variant v33 = v3 ^ v3; + t.fail("foo ^ foo --> error"); + } catch (...) { + t.pass("foo ^ foo --> error"); + } + + // string ^ date -> ERROR + try { + Variant v34 = v3 ^ v4; + t.fail("foo ^ 1234567890 --> error"); + } catch (...) { + t.pass("foo ^ 1234567890 --> error"); + } + + // string ^ duration -> ERROR + try { + Variant v35 = v3 ^ v5; + t.fail("foo ^ 1200 --> error"); + } catch (...) { + t.pass("foo ^ 1200 --> error"); + } + + // date ^ boolean -> ERROR + try { + Variant v40 = v4 ^ v0; + t.fail("1234567890 ^ true --> error"); + } catch (...) { + t.pass("1234567890 ^ true --> error"); + } + + // date ^ integer -> ERROR + try { + Variant v41 = v4 ^ v1; + t.fail("1234567890 ^ 42 --> error"); + } catch (...) { + t.pass("1234567890 ^ 42 --> error"); + } + + // date ^ real -> ERROR + try { + Variant v42 = v4 ^ v2; + t.fail("1234567890 ^ 3.14 --> error"); + } catch (...) { + t.pass("1234567890 ^ 3.14 --> error"); + } + + // date ^ string -> ERROR + try { + Variant v43 = v4 ^ v3; + t.fail("1234567890 ^ foo --> error"); + } catch (...) { + t.pass("1234567890 ^ foo --> error"); + } + + // date ^ date -> ERROR + try { + Variant v44 = v4 ^ v4; + t.fail("1234567890 ^ 1234567890 --> error"); + } catch (...) { + t.pass("1234567890 ^ 1234567890 --> error"); + } + + // date ^ duration -> ERROR + try { + Variant v45 = v4 ^ v5; + t.fail("1234567890 ^ 1200 --> error"); + } catch (...) { + t.pass("1234567890 ^ 1200 --> error"); + } + + // duration ^ boolean -> ERROR + try { + Variant v50 = v5 ^ v0; + t.fail("1200 ^ true --> error"); + } catch (...) { + t.pass("1200 ^ true --> error"); + } + + // duration ^ integer -> ERROR + try { + Variant v51 = v5 ^ v1; + t.fail("1200 ^ 42 --> error"); + } catch (...) { + t.pass("1200 ^ 42 --> error"); + } + + // duration ^ real -> ERROR + try { + Variant v52 = v5 ^ v2; + t.fail("1200 ^ 3.14 --> error"); + } catch (...) { + t.pass("1200 ^ 3.14 --> error"); + } + + // duration ^ string -> ERROR + try { + Variant v53 = v5 ^ v3; + t.fail("1200 ^ foo --> error"); + } catch (...) { + t.pass("1200 ^ foo --> error"); + } + + // duration ^ date -> ERROR + try { + Variant v54 = v5 ^ v4; + t.fail("1200 ^ 1234567890 --> error"); + } catch (...) { + t.pass("1200 ^ 1234567890 --> error"); + } + + // duration ^ duration -> ERROR + try { + Variant v55 = v5 ^ v5; + t.fail("1200 ^ 1200 --> error"); + } catch (...) { + t.pass("1200 ^ 1200 --> error"); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_gt.t.cpp b/test/variant_gt.t.cpp deleted file mode 100644 index 69dd44742..000000000 --- a/test/variant_gt.t.cpp +++ /dev/null @@ -1,194 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 > v0; - t.is (v00.type (), Variant::type_boolean, "true > true --> boolean"); - t.is (v00.get_bool (), false, "true > true --> false"); - - Variant v01 = v0 > v1; - t.is (v01.type (), Variant::type_boolean, "true > 42 --> boolean"); - t.is (v01.get_bool (), false, "true > 42 --> false"); - - Variant v02 = v0 > v2; - t.is (v02.type (), Variant::type_boolean, "true > 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true > 3.14 --> false"); - - Variant v03 = v0 > v3; - t.is (v03.type (), Variant::type_boolean, "true > 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true > 'foo' --> true"); - - Variant v04 = v0 > v4; - t.is (v04.type (), Variant::type_boolean, "true > 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true > 1234567890 --> false"); - - Variant v05 = v0 > v5; - t.is (v05.type (), Variant::type_boolean, "true > 1200 --> boolean"); - t.is (v05.get_bool (), false, "true > 1200 --> false"); - - Variant v10 = v1 > v0; - t.is (v10.type (), Variant::type_boolean, "42 > true --> boolean"); - t.is (v10.get_bool (), true, "42 > true --> true"); - - Variant v11 = v1 > v1; - t.is (v11.type (), Variant::type_boolean, "42 > 42 --> boolean"); - t.is (v11.get_bool (), false, "42 > 42 --> false"); - - Variant v12 = v1 > v2; - t.is (v12.type (), Variant::type_boolean, "42 > 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 > 3.14 --> true"); - - Variant v13 = v1 > v3; - t.is (v13.type (), Variant::type_boolean, "42 > 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 > 'foo' --> false"); - - Variant v14 = v1 > v4; - t.is (v04.type (), Variant::type_boolean, "42 > 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 > 1234567890 --> false"); - - Variant v15 = v1 > v5; - t.is (v15.type (), Variant::type_boolean, "42 > 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 > 1200 --> false"); - - Variant v20 = v2 > v0; - t.is (v20.type (), Variant::type_boolean, "3.14 > true --> boolean"); - t.is (v20.get_bool (), true, "3.14 > true --> true"); - - Variant v21 = v2 > v1; - t.is (v21.type (), Variant::type_boolean, "3.14 > 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 > 42 --> false"); - - Variant v22 = v2 > v2; - t.is (v22.type (), Variant::type_boolean, "3.14 > 3.14 --> boolean"); - t.is (v22.get_bool (), false, "3.14 > 3.14 --> false"); - - Variant v23 = v2 > v3; - t.is (v23.type (), Variant::type_boolean, "3.14 > 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 > 'foo' --> false"); - - Variant v24 = v2 > v4; - t.is (v24.type (), Variant::type_boolean, "3.14 > 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 > 1234567890 --> false"); - - Variant v25 = v2 > v5; - t.is (v25.type (), Variant::type_boolean, "3.14 > 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 > 1200 --> false"); - - Variant v30 = v3 > v0; - t.is (v30.type (), Variant::type_boolean, "'foo' > true --> boolean"); - t.is (v30.get_bool (), false, "'foo' > true --> false"); - - Variant v31 = v3 > v1; - t.is (v31.type (), Variant::type_boolean, "'foo' > 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' > 42 --> true"); - - Variant v32 = v3 > v2; - t.is (v32.type (), Variant::type_boolean, "'foo' > 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' > 3.14 --> true"); - - Variant v33 = v3 > v3; - t.is (v33.type (), Variant::type_boolean, "'foo' > 'foo' --> boolean"); - t.is (v33.get_bool (), false, "'foo' > 'foo' --> false"); - - Variant v34 = v3 > v4; - t.is (v34.type (), Variant::type_boolean, "'foo' > 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' > 1234567890 --> false"); - - Variant v35 = v3 > v5; - t.is (v35.type (), Variant::type_boolean, "'foo' > 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' > 1200 --> false"); - - Variant v40 = v4 > v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 > true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 > true --> true"); - - Variant v41 = v4 > v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 > 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 > 42 --> true"); - - Variant v42 = v4 > v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 > 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 > 3.14 --> true"); - - Variant v43 = v4 > v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 > 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 > 'foo' --> true"); - - Variant v44 = v4 > v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 > 1234567890 --> boolean"); - t.is (v44.get_bool (), false, "1234567890 > 1234567890 --> false"); - - Variant v45 = v4 > v5; - // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 - // (evaluated as now+1200s) be in future for any date after 2009-02-13 - t.is (v45.type (), Variant::type_boolean, "1234567890 > 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 > 1200 --> false"); - - Variant v50 = v5 > v0; - t.is (v50.type (), Variant::type_boolean, "1200 > true --> boolean"); - t.is (v50.get_bool (), true, "1200 > true --> true"); - - Variant v51 = v5 > v1; - t.is (v51.type (), Variant::type_boolean, "1200 > 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 > 42 --> true"); - - Variant v52 = v5 > v2; - t.is (v52.type (), Variant::type_boolean, "1200 > 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 > 3.14 --> true"); - - Variant v53 = v5 > v3; - t.is (v53.type (), Variant::type_boolean, "1200 > 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 > 'foo' --> true"); - - Variant v54 = v5 > v4; - // Same reasoning as v45 - t.is (v54.type (), Variant::type_boolean, "1200 > 1234567890 --> boolean"); - t.is (v54.get_bool (), true, "1200 > 1234567890 --> true"); - - Variant v55 = v5 > v5; - t.is (v55.type (), Variant::type_boolean, "1200 > 1200 --> boolean"); - t.is (v55.get_bool (), false, "1200 > 1200 --> false"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_gt_test.cpp b/test/variant_gt_test.cpp new file mode 100644 index 000000000..2abab3add --- /dev/null +++ b/test/variant_gt_test.cpp @@ -0,0 +1,196 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 > v0; + t.is(v00.type(), Variant::type_boolean, "true > true --> boolean"); + t.is(v00.get_bool(), false, "true > true --> false"); + + Variant v01 = v0 > v1; + t.is(v01.type(), Variant::type_boolean, "true > 42 --> boolean"); + t.is(v01.get_bool(), false, "true > 42 --> false"); + + Variant v02 = v0 > v2; + t.is(v02.type(), Variant::type_boolean, "true > 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true > 3.14 --> false"); + + Variant v03 = v0 > v3; + t.is(v03.type(), Variant::type_boolean, "true > 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true > 'foo' --> true"); + + Variant v04 = v0 > v4; + t.is(v04.type(), Variant::type_boolean, "true > 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true > 1234567890 --> false"); + + Variant v05 = v0 > v5; + t.is(v05.type(), Variant::type_boolean, "true > 1200 --> boolean"); + t.is(v05.get_bool(), false, "true > 1200 --> false"); + + Variant v10 = v1 > v0; + t.is(v10.type(), Variant::type_boolean, "42 > true --> boolean"); + t.is(v10.get_bool(), true, "42 > true --> true"); + + Variant v11 = v1 > v1; + t.is(v11.type(), Variant::type_boolean, "42 > 42 --> boolean"); + t.is(v11.get_bool(), false, "42 > 42 --> false"); + + Variant v12 = v1 > v2; + t.is(v12.type(), Variant::type_boolean, "42 > 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 > 3.14 --> true"); + + Variant v13 = v1 > v3; + t.is(v13.type(), Variant::type_boolean, "42 > 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 > 'foo' --> false"); + + Variant v14 = v1 > v4; + t.is(v04.type(), Variant::type_boolean, "42 > 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 > 1234567890 --> false"); + + Variant v15 = v1 > v5; + t.is(v15.type(), Variant::type_boolean, "42 > 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 > 1200 --> false"); + + Variant v20 = v2 > v0; + t.is(v20.type(), Variant::type_boolean, "3.14 > true --> boolean"); + t.is(v20.get_bool(), true, "3.14 > true --> true"); + + Variant v21 = v2 > v1; + t.is(v21.type(), Variant::type_boolean, "3.14 > 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 > 42 --> false"); + + Variant v22 = v2 > v2; + t.is(v22.type(), Variant::type_boolean, "3.14 > 3.14 --> boolean"); + t.is(v22.get_bool(), false, "3.14 > 3.14 --> false"); + + Variant v23 = v2 > v3; + t.is(v23.type(), Variant::type_boolean, "3.14 > 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 > 'foo' --> false"); + + Variant v24 = v2 > v4; + t.is(v24.type(), Variant::type_boolean, "3.14 > 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 > 1234567890 --> false"); + + Variant v25 = v2 > v5; + t.is(v25.type(), Variant::type_boolean, "3.14 > 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 > 1200 --> false"); + + Variant v30 = v3 > v0; + t.is(v30.type(), Variant::type_boolean, "'foo' > true --> boolean"); + t.is(v30.get_bool(), false, "'foo' > true --> false"); + + Variant v31 = v3 > v1; + t.is(v31.type(), Variant::type_boolean, "'foo' > 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' > 42 --> true"); + + Variant v32 = v3 > v2; + t.is(v32.type(), Variant::type_boolean, "'foo' > 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' > 3.14 --> true"); + + Variant v33 = v3 > v3; + t.is(v33.type(), Variant::type_boolean, "'foo' > 'foo' --> boolean"); + t.is(v33.get_bool(), false, "'foo' > 'foo' --> false"); + + Variant v34 = v3 > v4; + t.is(v34.type(), Variant::type_boolean, "'foo' > 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' > 1234567890 --> false"); + + Variant v35 = v3 > v5; + t.is(v35.type(), Variant::type_boolean, "'foo' > 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' > 1200 --> false"); + + Variant v40 = v4 > v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 > true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 > true --> true"); + + Variant v41 = v4 > v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 > 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 > 42 --> true"); + + Variant v42 = v4 > v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 > 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 > 3.14 --> true"); + + Variant v43 = v4 > v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 > 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 > 'foo' --> true"); + + Variant v44 = v4 > v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 > 1234567890 --> boolean"); + t.is(v44.get_bool(), false, "1234567890 > 1234567890 --> false"); + + Variant v45 = v4 > v5; + // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 + // (evaluated as now+1200s) be in future for any date after 2009-02-13 + t.is(v45.type(), Variant::type_boolean, "1234567890 > 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 > 1200 --> false"); + + Variant v50 = v5 > v0; + t.is(v50.type(), Variant::type_boolean, "1200 > true --> boolean"); + t.is(v50.get_bool(), true, "1200 > true --> true"); + + Variant v51 = v5 > v1; + t.is(v51.type(), Variant::type_boolean, "1200 > 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 > 42 --> true"); + + Variant v52 = v5 > v2; + t.is(v52.type(), Variant::type_boolean, "1200 > 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 > 3.14 --> true"); + + Variant v53 = v5 > v3; + t.is(v53.type(), Variant::type_boolean, "1200 > 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 > 'foo' --> true"); + + Variant v54 = v5 > v4; + // Same reasoning as v45 + t.is(v54.type(), Variant::type_boolean, "1200 > 1234567890 --> boolean"); + t.is(v54.get_bool(), true, "1200 > 1234567890 --> true"); + + Variant v55 = v5 > v5; + t.is(v55.type(), Variant::type_boolean, "1200 > 1200 --> boolean"); + t.is(v55.get_bool(), false, "1200 > 1200 --> false"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_gte.t.cpp b/test/variant_gte.t.cpp deleted file mode 100644 index 093358f0e..000000000 --- a/test/variant_gte.t.cpp +++ /dev/null @@ -1,194 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 >= v0; - t.is (v00.type (), Variant::type_boolean, "true >= true --> boolean"); - t.is (v00.get_bool (), true, "true >= true --> true"); - - Variant v01 = v0 >= v1; - t.is (v01.type (), Variant::type_boolean, "true >= 42 --> boolean"); - t.is (v01.get_bool (), false, "true >= 42 --> false"); - - Variant v02 = v0 >= v2; - t.is (v02.type (), Variant::type_boolean, "true >= 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true >= 3.14 --> false"); - - Variant v03 = v0 >= v3; - t.is (v03.type (), Variant::type_boolean, "true >= 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true >= 'foo' --> true"); - - Variant v04 = v0 >= v4; - t.is (v04.type (), Variant::type_boolean, "true >= 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true >= 1234567890 --> false"); - - Variant v05 = v0 >= v5; - t.is (v05.type (), Variant::type_boolean, "true >= 1200 --> boolean"); - t.is (v05.get_bool (), false, "true >= 1200 --> false"); - - Variant v10 = v1 >= v0; - t.is (v10.type (), Variant::type_boolean, "42 >= true --> boolean"); - t.is (v10.get_bool (), true, "42 >= true --> true"); - - Variant v11 = v1 >= v1; - t.is (v11.type (), Variant::type_boolean, "42 >= 42 --> boolean"); - t.is (v11.get_bool (), true, "42 >= 42 --> true"); - - Variant v12 = v1 >= v2; - t.is (v12.type (), Variant::type_boolean, "42 >= 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 >= 3.14 --> true"); - - Variant v13 = v1 >= v3; - t.is (v13.type (), Variant::type_boolean, "42 >= 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 >= 'foo' --> false"); - - Variant v14 = v1 >= v4; - t.is (v04.type (), Variant::type_boolean, "42 >= 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 >= 1234567890 --> false"); - - Variant v15 = v1 >= v5; - t.is (v15.type (), Variant::type_boolean, "42 >= 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 >= 1200 --> false"); - - Variant v20 = v2 >= v0; - t.is (v20.type (), Variant::type_boolean, "3.14 >= true --> boolean"); - t.is (v20.get_bool (), true, "3.14 >= true --> true"); - - Variant v21 = v2 >= v1; - t.is (v21.type (), Variant::type_boolean, "3.14 >= 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 >= 42 --> false"); - - Variant v22 = v2 >= v2; - t.is (v22.type (), Variant::type_boolean, "3.14 >= 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 >= 3.14 --> true"); - - Variant v23 = v2 >= v3; - t.is (v23.type (), Variant::type_boolean, "3.14 >= 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 >= 'foo' --> false"); - - Variant v24 = v2 >= v4; - t.is (v24.type (), Variant::type_boolean, "3.14 >= 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 >= 1234567890 --> false"); - - Variant v25 = v2 >= v5; - t.is (v25.type (), Variant::type_boolean, "3.14 >= 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 >= 1200 --> false"); - - Variant v30 = v3 >= v0; - t.is (v30.type (), Variant::type_boolean, "'foo' >= true --> boolean"); - t.is (v30.get_bool (), false, "'foo' >= true --> false"); - - Variant v31 = v3 >= v1; - t.is (v31.type (), Variant::type_boolean, "'foo' >= 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' >= 42 --> true"); - - Variant v32 = v3 >= v2; - t.is (v32.type (), Variant::type_boolean, "'foo' >= 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' >= 3.14 --> true"); - - Variant v33 = v3 >= v3; - t.is (v33.type (), Variant::type_boolean, "'foo' >= 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' >= 'foo' --> true"); - - Variant v34 = v3 >= v4; - t.is (v34.type (), Variant::type_boolean, "'foo' >= 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' >= 1234567890 --> false"); - - Variant v35 = v3 >= v5; - t.is (v35.type (), Variant::type_boolean, "'foo' >= 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' >= 1200 --> false"); - - Variant v40 = v4 >= v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 >= true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 >= true --> true"); - - Variant v41 = v4 >= v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 >= 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 >= 42 --> true"); - - Variant v42 = v4 >= v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 >= 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 >= 3.14 --> true"); - - Variant v43 = v4 >= v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 >= 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 >= 'foo' --> true"); - - Variant v44 = v4 >= v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 >= 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 >= 1234567890 --> true"); - - Variant v45 = v4 >= v5; - // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 - // (evaluated as now+1200s) be in future for any date after 2009-02-13 - t.is (v45.type (), Variant::type_boolean, "1234567890 >= 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 >= 1200 --> false"); - - Variant v50 = v5 >= v0; - t.is (v50.type (), Variant::type_boolean, "1200 >= true --> boolean"); - t.is (v50.get_bool (), true, "1200 >= true --> true"); - - Variant v51 = v5 >= v1; - t.is (v51.type (), Variant::type_boolean, "1200 >= 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 >= 42 --> true"); - - Variant v52 = v5 >= v2; - t.is (v52.type (), Variant::type_boolean, "1200 >= 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 >= 3.14 --> true"); - - Variant v53 = v5 >= v3; - t.is (v53.type (), Variant::type_boolean, "1200 >= 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 >= 'foo' --> true"); - - Variant v54 = v5 >= v4; - // Same reasoning as v45 - t.is (v54.type (), Variant::type_boolean, "1200 >= 1234567890 --> boolean"); - t.is (v54.get_bool (), true, "1200 >= 1234567890 --> true"); - - Variant v55 = v5 >= v5; - t.is (v55.type (), Variant::type_boolean, "1200 >= 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 >= 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_gte_test.cpp b/test/variant_gte_test.cpp new file mode 100644 index 000000000..4dbfa8f8e --- /dev/null +++ b/test/variant_gte_test.cpp @@ -0,0 +1,196 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 >= v0; + t.is(v00.type(), Variant::type_boolean, "true >= true --> boolean"); + t.is(v00.get_bool(), true, "true >= true --> true"); + + Variant v01 = v0 >= v1; + t.is(v01.type(), Variant::type_boolean, "true >= 42 --> boolean"); + t.is(v01.get_bool(), false, "true >= 42 --> false"); + + Variant v02 = v0 >= v2; + t.is(v02.type(), Variant::type_boolean, "true >= 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true >= 3.14 --> false"); + + Variant v03 = v0 >= v3; + t.is(v03.type(), Variant::type_boolean, "true >= 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true >= 'foo' --> true"); + + Variant v04 = v0 >= v4; + t.is(v04.type(), Variant::type_boolean, "true >= 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true >= 1234567890 --> false"); + + Variant v05 = v0 >= v5; + t.is(v05.type(), Variant::type_boolean, "true >= 1200 --> boolean"); + t.is(v05.get_bool(), false, "true >= 1200 --> false"); + + Variant v10 = v1 >= v0; + t.is(v10.type(), Variant::type_boolean, "42 >= true --> boolean"); + t.is(v10.get_bool(), true, "42 >= true --> true"); + + Variant v11 = v1 >= v1; + t.is(v11.type(), Variant::type_boolean, "42 >= 42 --> boolean"); + t.is(v11.get_bool(), true, "42 >= 42 --> true"); + + Variant v12 = v1 >= v2; + t.is(v12.type(), Variant::type_boolean, "42 >= 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 >= 3.14 --> true"); + + Variant v13 = v1 >= v3; + t.is(v13.type(), Variant::type_boolean, "42 >= 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 >= 'foo' --> false"); + + Variant v14 = v1 >= v4; + t.is(v04.type(), Variant::type_boolean, "42 >= 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 >= 1234567890 --> false"); + + Variant v15 = v1 >= v5; + t.is(v15.type(), Variant::type_boolean, "42 >= 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 >= 1200 --> false"); + + Variant v20 = v2 >= v0; + t.is(v20.type(), Variant::type_boolean, "3.14 >= true --> boolean"); + t.is(v20.get_bool(), true, "3.14 >= true --> true"); + + Variant v21 = v2 >= v1; + t.is(v21.type(), Variant::type_boolean, "3.14 >= 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 >= 42 --> false"); + + Variant v22 = v2 >= v2; + t.is(v22.type(), Variant::type_boolean, "3.14 >= 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 >= 3.14 --> true"); + + Variant v23 = v2 >= v3; + t.is(v23.type(), Variant::type_boolean, "3.14 >= 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 >= 'foo' --> false"); + + Variant v24 = v2 >= v4; + t.is(v24.type(), Variant::type_boolean, "3.14 >= 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 >= 1234567890 --> false"); + + Variant v25 = v2 >= v5; + t.is(v25.type(), Variant::type_boolean, "3.14 >= 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 >= 1200 --> false"); + + Variant v30 = v3 >= v0; + t.is(v30.type(), Variant::type_boolean, "'foo' >= true --> boolean"); + t.is(v30.get_bool(), false, "'foo' >= true --> false"); + + Variant v31 = v3 >= v1; + t.is(v31.type(), Variant::type_boolean, "'foo' >= 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' >= 42 --> true"); + + Variant v32 = v3 >= v2; + t.is(v32.type(), Variant::type_boolean, "'foo' >= 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' >= 3.14 --> true"); + + Variant v33 = v3 >= v3; + t.is(v33.type(), Variant::type_boolean, "'foo' >= 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' >= 'foo' --> true"); + + Variant v34 = v3 >= v4; + t.is(v34.type(), Variant::type_boolean, "'foo' >= 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' >= 1234567890 --> false"); + + Variant v35 = v3 >= v5; + t.is(v35.type(), Variant::type_boolean, "'foo' >= 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' >= 1200 --> false"); + + Variant v40 = v4 >= v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 >= true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 >= true --> true"); + + Variant v41 = v4 >= v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 >= 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 >= 42 --> true"); + + Variant v42 = v4 >= v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 >= 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 >= 3.14 --> true"); + + Variant v43 = v4 >= v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 >= 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 >= 'foo' --> true"); + + Variant v44 = v4 >= v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 >= 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 >= 1234567890 --> true"); + + Variant v45 = v4 >= v5; + // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 + // (evaluated as now+1200s) be in future for any date after 2009-02-13 + t.is(v45.type(), Variant::type_boolean, "1234567890 >= 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 >= 1200 --> false"); + + Variant v50 = v5 >= v0; + t.is(v50.type(), Variant::type_boolean, "1200 >= true --> boolean"); + t.is(v50.get_bool(), true, "1200 >= true --> true"); + + Variant v51 = v5 >= v1; + t.is(v51.type(), Variant::type_boolean, "1200 >= 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 >= 42 --> true"); + + Variant v52 = v5 >= v2; + t.is(v52.type(), Variant::type_boolean, "1200 >= 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 >= 3.14 --> true"); + + Variant v53 = v5 >= v3; + t.is(v53.type(), Variant::type_boolean, "1200 >= 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 >= 'foo' --> true"); + + Variant v54 = v5 >= v4; + // Same reasoning as v45 + t.is(v54.type(), Variant::type_boolean, "1200 >= 1234567890 --> boolean"); + t.is(v54.get_bool(), true, "1200 >= 1234567890 --> true"); + + Variant v55 = v5 >= v5; + t.is(v55.type(), Variant::type_boolean, "1200 >= 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 >= 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_inequal.t.cpp b/test/variant_inequal.t.cpp deleted file mode 100644 index 74c91b965..000000000 --- a/test/variant_inequal.t.cpp +++ /dev/null @@ -1,191 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 != v0; - t.is (v00.type (), Variant::type_boolean, "true != true --> boolean"); - t.is (v00.get_bool (), false, "true != true --> false"); - - Variant v01 = v0 != v1; - t.is (v01.type (), Variant::type_boolean, "true != 42 --> boolean"); - t.is (v01.get_bool (), true, "true != 42 --> true"); - - Variant v02 = v0 != v2; - t.is (v02.type (), Variant::type_boolean, "true != 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true != 3.14 --> true"); - - Variant v03 = v0 != v3; - t.is (v03.type (), Variant::type_boolean, "true != 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true != 'foo' --> true"); - - Variant v04 = v0 != v4; - t.is (v04.type (), Variant::type_boolean, "true != 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true != 1234567890 --> true"); - - Variant v05 = v0 != v5; - t.is (v05.type (), Variant::type_boolean, "true != 1200 --> boolean"); - t.is (v05.get_bool (), true, "true != 1200 --> true"); - - Variant v10 = v1 != v0; - t.is (v10.type (), Variant::type_boolean, "42 != true --> boolean"); - t.is (v10.get_bool (), true, "42 != true --> true"); - - Variant v11 = v1 != v1; - t.is (v11.type (), Variant::type_boolean, "42 != 42 --> boolean"); - t.is (v11.get_bool (), false, "42 != 42 --> false"); - - Variant v12 = v1 != v2; - t.is (v12.type (), Variant::type_boolean, "42 != 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 != 3.14 --> true"); - - Variant v13 = v1 != v3; - t.is (v13.type (), Variant::type_boolean, "42 != 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 != 'foo' --> true"); - - Variant v14 = v1 != v4; - t.is (v04.type (), Variant::type_boolean, "42 != 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 != 1234567890 --> true"); - - Variant v15 = v1 != v5; - t.is (v15.type (), Variant::type_boolean, "42 != 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 != 1200 --> true"); - - Variant v20 = v2 != v0; - t.is (v20.type (), Variant::type_boolean, "3.14 != true --> boolean"); - t.is (v20.get_bool (), true, "3.14 != true --> true"); - - Variant v21 = v2 != v1; - t.is (v21.type (), Variant::type_boolean, "3.14 != 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 != 42 --> true"); - - Variant v22 = v2 != v2; - t.is (v22.type (), Variant::type_boolean, "3.14 != 3.14 --> boolean"); - t.is (v22.get_bool (), false, "3.14 != 3.14 --> false"); - - Variant v23 = v2 != v3; - t.is (v23.type (), Variant::type_boolean, "3.14 != 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 != 'foo' --> true"); - - Variant v24 = v2 != v4; - t.is (v24.type (), Variant::type_boolean, "3.14 != 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 != 1234567890 --> true"); - - Variant v25 = v2 != v5; - t.is (v25.type (), Variant::type_boolean, "3.14 != 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 != 1200 --> true"); - - Variant v30 = v3 != v0; - t.is (v30.type (), Variant::type_boolean, "'foo' != true --> boolean"); - t.is (v30.get_bool (), true, "'foo' != true --> true"); - - Variant v31 = v3 != v1; - t.is (v31.type (), Variant::type_boolean, "'foo' != 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' != 42 --> true"); - - Variant v32 = v3 != v2; - t.is (v32.type (), Variant::type_boolean, "'foo' != 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' != 3.14 --> true"); - - Variant v33 = v3 != v3; - t.is (v33.type (), Variant::type_boolean, "'foo' != 'foo' --> boolean"); - t.is (v33.get_bool (), false, "'foo' != 'foo' --> false"); - - Variant v34 = v3 != v4; - t.is (v34.type (), Variant::type_boolean, "'foo' != 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' != 1234567890 --> true"); - - Variant v35 = v3 != v5; - t.is (v35.type (), Variant::type_boolean, "'foo' != 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' != 1200 --> true"); - - Variant v40 = v4 != v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 != true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 != true --> true"); - - Variant v41 = v4 != v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 != 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 != 42 --> true"); - - Variant v42 = v4 != v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 != 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 != 3.14 --> true"); - - Variant v43 = v4 != v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 != 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 != 'foo' --> true"); - - Variant v44 = v4 != v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 != 1234567890 --> boolean"); - t.is (v44.get_bool (), false, "1234567890 != 1234567890 --> false"); - - Variant v45 = v4 != v5; - t.is (v45.type (), Variant::type_boolean, "1234567890 != 1200 --> boolean"); - t.is (v45.get_bool (), true, "1234567890 != 1200 --> true"); - - Variant v50 = v5 != v0; - t.is (v50.type (), Variant::type_boolean, "1200 != true --> boolean"); - t.is (v50.get_bool (), true, "1200 != true --> true"); - - Variant v51 = v5 != v1; - t.is (v51.type (), Variant::type_boolean, "1200 != 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 != 42 --> true"); - - Variant v52 = v5 != v2; - t.is (v52.type (), Variant::type_boolean, "1200 != 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 != 3.14 --> true"); - - Variant v53 = v5 != v3; - t.is (v53.type (), Variant::type_boolean, "1200 != 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 != 'foo' --> true"); - - Variant v54 = v5 != v4; - t.is (v04.type (), Variant::type_boolean, "1200 != 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "1200 != 1234567890 --> true"); - - Variant v55 = v5 != v5; - t.is (v55.type (), Variant::type_boolean, "1200 != 1200 --> boolean"); - t.is (v55.get_bool (), false, "1200 != 1200 --> false"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_inequal_test.cpp b/test/variant_inequal_test.cpp new file mode 100644 index 000000000..29222899b --- /dev/null +++ b/test/variant_inequal_test.cpp @@ -0,0 +1,193 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 != v0; + t.is(v00.type(), Variant::type_boolean, "true != true --> boolean"); + t.is(v00.get_bool(), false, "true != true --> false"); + + Variant v01 = v0 != v1; + t.is(v01.type(), Variant::type_boolean, "true != 42 --> boolean"); + t.is(v01.get_bool(), true, "true != 42 --> true"); + + Variant v02 = v0 != v2; + t.is(v02.type(), Variant::type_boolean, "true != 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true != 3.14 --> true"); + + Variant v03 = v0 != v3; + t.is(v03.type(), Variant::type_boolean, "true != 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true != 'foo' --> true"); + + Variant v04 = v0 != v4; + t.is(v04.type(), Variant::type_boolean, "true != 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true != 1234567890 --> true"); + + Variant v05 = v0 != v5; + t.is(v05.type(), Variant::type_boolean, "true != 1200 --> boolean"); + t.is(v05.get_bool(), true, "true != 1200 --> true"); + + Variant v10 = v1 != v0; + t.is(v10.type(), Variant::type_boolean, "42 != true --> boolean"); + t.is(v10.get_bool(), true, "42 != true --> true"); + + Variant v11 = v1 != v1; + t.is(v11.type(), Variant::type_boolean, "42 != 42 --> boolean"); + t.is(v11.get_bool(), false, "42 != 42 --> false"); + + Variant v12 = v1 != v2; + t.is(v12.type(), Variant::type_boolean, "42 != 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 != 3.14 --> true"); + + Variant v13 = v1 != v3; + t.is(v13.type(), Variant::type_boolean, "42 != 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 != 'foo' --> true"); + + Variant v14 = v1 != v4; + t.is(v04.type(), Variant::type_boolean, "42 != 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 != 1234567890 --> true"); + + Variant v15 = v1 != v5; + t.is(v15.type(), Variant::type_boolean, "42 != 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 != 1200 --> true"); + + Variant v20 = v2 != v0; + t.is(v20.type(), Variant::type_boolean, "3.14 != true --> boolean"); + t.is(v20.get_bool(), true, "3.14 != true --> true"); + + Variant v21 = v2 != v1; + t.is(v21.type(), Variant::type_boolean, "3.14 != 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 != 42 --> true"); + + Variant v22 = v2 != v2; + t.is(v22.type(), Variant::type_boolean, "3.14 != 3.14 --> boolean"); + t.is(v22.get_bool(), false, "3.14 != 3.14 --> false"); + + Variant v23 = v2 != v3; + t.is(v23.type(), Variant::type_boolean, "3.14 != 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 != 'foo' --> true"); + + Variant v24 = v2 != v4; + t.is(v24.type(), Variant::type_boolean, "3.14 != 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 != 1234567890 --> true"); + + Variant v25 = v2 != v5; + t.is(v25.type(), Variant::type_boolean, "3.14 != 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 != 1200 --> true"); + + Variant v30 = v3 != v0; + t.is(v30.type(), Variant::type_boolean, "'foo' != true --> boolean"); + t.is(v30.get_bool(), true, "'foo' != true --> true"); + + Variant v31 = v3 != v1; + t.is(v31.type(), Variant::type_boolean, "'foo' != 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' != 42 --> true"); + + Variant v32 = v3 != v2; + t.is(v32.type(), Variant::type_boolean, "'foo' != 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' != 3.14 --> true"); + + Variant v33 = v3 != v3; + t.is(v33.type(), Variant::type_boolean, "'foo' != 'foo' --> boolean"); + t.is(v33.get_bool(), false, "'foo' != 'foo' --> false"); + + Variant v34 = v3 != v4; + t.is(v34.type(), Variant::type_boolean, "'foo' != 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' != 1234567890 --> true"); + + Variant v35 = v3 != v5; + t.is(v35.type(), Variant::type_boolean, "'foo' != 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' != 1200 --> true"); + + Variant v40 = v4 != v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 != true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 != true --> true"); + + Variant v41 = v4 != v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 != 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 != 42 --> true"); + + Variant v42 = v4 != v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 != 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 != 3.14 --> true"); + + Variant v43 = v4 != v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 != 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 != 'foo' --> true"); + + Variant v44 = v4 != v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 != 1234567890 --> boolean"); + t.is(v44.get_bool(), false, "1234567890 != 1234567890 --> false"); + + Variant v45 = v4 != v5; + t.is(v45.type(), Variant::type_boolean, "1234567890 != 1200 --> boolean"); + t.is(v45.get_bool(), true, "1234567890 != 1200 --> true"); + + Variant v50 = v5 != v0; + t.is(v50.type(), Variant::type_boolean, "1200 != true --> boolean"); + t.is(v50.get_bool(), true, "1200 != true --> true"); + + Variant v51 = v5 != v1; + t.is(v51.type(), Variant::type_boolean, "1200 != 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 != 42 --> true"); + + Variant v52 = v5 != v2; + t.is(v52.type(), Variant::type_boolean, "1200 != 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 != 3.14 --> true"); + + Variant v53 = v5 != v3; + t.is(v53.type(), Variant::type_boolean, "1200 != 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 != 'foo' --> true"); + + Variant v54 = v5 != v4; + t.is(v04.type(), Variant::type_boolean, "1200 != 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "1200 != 1234567890 --> true"); + + Variant v55 = v5 != v5; + t.is(v55.type(), Variant::type_boolean, "1200 != 1200 --> boolean"); + t.is(v55.get_bool(), false, "1200 != 1200 --> false"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_lt.t.cpp b/test/variant_lt.t.cpp deleted file mode 100644 index 56394f4c9..000000000 --- a/test/variant_lt.t.cpp +++ /dev/null @@ -1,194 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 < v0; - t.is (v00.type (), Variant::type_boolean, "true < true --> boolean"); - t.is (v00.get_bool (), false, "true < true --> false"); - - Variant v01 = v0 < v1; - t.is (v01.type (), Variant::type_boolean, "true < 42 --> boolean"); - t.is (v01.get_bool (), true, "true < 42 --> true"); - - Variant v02 = v0 < v2; - t.is (v02.type (), Variant::type_boolean, "true < 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true < 3.14 --> true"); - - Variant v03 = v0 < v3; - t.is (v03.type (), Variant::type_boolean, "true < 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true < 'foo' --> false"); - - Variant v04 = v0 < v4; - t.is (v04.type (), Variant::type_boolean, "true < 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true < 1234567890 --> true"); - - Variant v05 = v0 < v5; - t.is (v05.type (), Variant::type_boolean, "true < 1200 --> boolean"); - t.is (v05.get_bool (), true, "true < 1200 --> true"); - - Variant v10 = v1 < v0; - t.is (v10.type (), Variant::type_boolean, "42 < true --> boolean"); - t.is (v10.get_bool (), false, "42 < true --> false"); - - Variant v11 = v1 < v1; - t.is (v11.type (), Variant::type_boolean, "42 < 42 --> boolean"); - t.is (v11.get_bool (), false, "42 < 42 --> false"); - - Variant v12 = v1 < v2; - t.is (v12.type (), Variant::type_boolean, "42 < 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 < 3.14 --> false"); - - Variant v13 = v1 < v3; - t.is (v13.type (), Variant::type_boolean, "42 < 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 < 'foo' --> true"); - - Variant v14 = v1 < v4; - t.is (v04.type (), Variant::type_boolean, "42 < 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 < 1234567890 --> true"); - - Variant v15 = v1 < v5; - t.is (v15.type (), Variant::type_boolean, "42 < 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 < 1200 --> true"); - - Variant v20 = v2 < v0; - t.is (v20.type (), Variant::type_boolean, "3.14 < true --> boolean"); - t.is (v20.get_bool (), false, "3.14 < true --> false"); - - Variant v21 = v2 < v1; - t.is (v21.type (), Variant::type_boolean, "3.14 < 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 < 42 --> true"); - - Variant v22 = v2 < v2; - t.is (v22.type (), Variant::type_boolean, "3.14 < 3.14 --> boolean"); - t.is (v22.get_bool (), false, "3.14 < 3.14 --> false"); - - Variant v23 = v2 < v3; - t.is (v23.type (), Variant::type_boolean, "3.14 < 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 < 'foo' --> true"); - - Variant v24 = v2 < v4; - t.is (v24.type (), Variant::type_boolean, "3.14 < 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 < 1234567890 --> true"); - - Variant v25 = v2 < v5; - t.is (v25.type (), Variant::type_boolean, "3.14 < 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 < 1200 --> true"); - - Variant v30 = v3 < v0; - t.is (v30.type (), Variant::type_boolean, "'foo' < true --> boolean"); - t.is (v30.get_bool (), true, "'foo' < true --> true"); - - Variant v31 = v3 < v1; - t.is (v31.type (), Variant::type_boolean, "'foo' < 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' < 42 --> false"); - - Variant v32 = v3 < v2; - t.is (v32.type (), Variant::type_boolean, "'foo' < 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' < 3.14 --> false"); - - Variant v33 = v3 < v3; - t.is (v33.type (), Variant::type_boolean, "'foo' < 'foo' --> boolean"); - t.is (v33.get_bool (), false, "'foo' < 'foo' --> false"); - - Variant v34 = v3 < v4; - t.is (v34.type (), Variant::type_boolean, "'foo' < 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' < 1234567890 --> true"); - - Variant v35 = v3 < v5; - t.is (v35.type (), Variant::type_boolean, "'foo' < 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' < 1200 --> true"); - - Variant v40 = v4 < v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 < true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 < true --> false"); - - Variant v41 = v4 < v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 < 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 < 42 --> false"); - - Variant v42 = v4 < v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 < 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 < 3.14 --> false"); - - Variant v43 = v4 < v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 < 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 < 'foo' --> false"); - - Variant v44 = v4 < v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 < 1234567890 --> boolean"); - t.is (v44.get_bool (), false, "1234567890 < 1234567890 --> false"); - - Variant v45 = v4 < v5; - // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 - // (evaluated as now+1200s) be in future for any date after 2009-02-13 - t.is (v45.type (), Variant::type_boolean, "1234567890 < 1200 --> boolean"); - t.is (v45.get_bool (), true, "1234567890 < 1200 --> true"); - - Variant v50 = v5 < v0; - t.is (v50.type (), Variant::type_boolean, "1200 < true --> boolean"); - t.is (v50.get_bool (), false, "1200 < true --> false"); - - Variant v51 = v5 < v1; - t.is (v51.type (), Variant::type_boolean, "1200 < 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 < 42 --> false"); - - Variant v52 = v5 < v2; - t.is (v52.type (), Variant::type_boolean, "1200 < 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 < 3.14 --> false"); - - Variant v53 = v5 < v3; - t.is (v53.type (), Variant::type_boolean, "1200 < 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 < 'foo' --> false"); - - Variant v54 = v5 < v4; - // Same logic as v45. - t.is (v54.type (), Variant::type_boolean, "1200 < 1234567890 --> boolean"); - t.is (v54.get_bool (), false, "1200 < 1234567890 --> false"); - - Variant v55 = v5 < v5; - t.is (v55.type (), Variant::type_boolean, "1200 < 1200 --> boolean"); - t.is (v55.get_bool (), false, "1200 < 1200 --> false"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_lt_test.cpp b/test/variant_lt_test.cpp new file mode 100644 index 000000000..80967edd5 --- /dev/null +++ b/test/variant_lt_test.cpp @@ -0,0 +1,196 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 < v0; + t.is(v00.type(), Variant::type_boolean, "true < true --> boolean"); + t.is(v00.get_bool(), false, "true < true --> false"); + + Variant v01 = v0 < v1; + t.is(v01.type(), Variant::type_boolean, "true < 42 --> boolean"); + t.is(v01.get_bool(), true, "true < 42 --> true"); + + Variant v02 = v0 < v2; + t.is(v02.type(), Variant::type_boolean, "true < 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true < 3.14 --> true"); + + Variant v03 = v0 < v3; + t.is(v03.type(), Variant::type_boolean, "true < 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true < 'foo' --> false"); + + Variant v04 = v0 < v4; + t.is(v04.type(), Variant::type_boolean, "true < 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true < 1234567890 --> true"); + + Variant v05 = v0 < v5; + t.is(v05.type(), Variant::type_boolean, "true < 1200 --> boolean"); + t.is(v05.get_bool(), true, "true < 1200 --> true"); + + Variant v10 = v1 < v0; + t.is(v10.type(), Variant::type_boolean, "42 < true --> boolean"); + t.is(v10.get_bool(), false, "42 < true --> false"); + + Variant v11 = v1 < v1; + t.is(v11.type(), Variant::type_boolean, "42 < 42 --> boolean"); + t.is(v11.get_bool(), false, "42 < 42 --> false"); + + Variant v12 = v1 < v2; + t.is(v12.type(), Variant::type_boolean, "42 < 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 < 3.14 --> false"); + + Variant v13 = v1 < v3; + t.is(v13.type(), Variant::type_boolean, "42 < 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 < 'foo' --> true"); + + Variant v14 = v1 < v4; + t.is(v04.type(), Variant::type_boolean, "42 < 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 < 1234567890 --> true"); + + Variant v15 = v1 < v5; + t.is(v15.type(), Variant::type_boolean, "42 < 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 < 1200 --> true"); + + Variant v20 = v2 < v0; + t.is(v20.type(), Variant::type_boolean, "3.14 < true --> boolean"); + t.is(v20.get_bool(), false, "3.14 < true --> false"); + + Variant v21 = v2 < v1; + t.is(v21.type(), Variant::type_boolean, "3.14 < 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 < 42 --> true"); + + Variant v22 = v2 < v2; + t.is(v22.type(), Variant::type_boolean, "3.14 < 3.14 --> boolean"); + t.is(v22.get_bool(), false, "3.14 < 3.14 --> false"); + + Variant v23 = v2 < v3; + t.is(v23.type(), Variant::type_boolean, "3.14 < 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 < 'foo' --> true"); + + Variant v24 = v2 < v4; + t.is(v24.type(), Variant::type_boolean, "3.14 < 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 < 1234567890 --> true"); + + Variant v25 = v2 < v5; + t.is(v25.type(), Variant::type_boolean, "3.14 < 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 < 1200 --> true"); + + Variant v30 = v3 < v0; + t.is(v30.type(), Variant::type_boolean, "'foo' < true --> boolean"); + t.is(v30.get_bool(), true, "'foo' < true --> true"); + + Variant v31 = v3 < v1; + t.is(v31.type(), Variant::type_boolean, "'foo' < 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' < 42 --> false"); + + Variant v32 = v3 < v2; + t.is(v32.type(), Variant::type_boolean, "'foo' < 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' < 3.14 --> false"); + + Variant v33 = v3 < v3; + t.is(v33.type(), Variant::type_boolean, "'foo' < 'foo' --> boolean"); + t.is(v33.get_bool(), false, "'foo' < 'foo' --> false"); + + Variant v34 = v3 < v4; + t.is(v34.type(), Variant::type_boolean, "'foo' < 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' < 1234567890 --> true"); + + Variant v35 = v3 < v5; + t.is(v35.type(), Variant::type_boolean, "'foo' < 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' < 1200 --> true"); + + Variant v40 = v4 < v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 < true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 < true --> false"); + + Variant v41 = v4 < v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 < 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 < 42 --> false"); + + Variant v42 = v4 < v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 < 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 < 3.14 --> false"); + + Variant v43 = v4 < v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 < 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 < 'foo' --> false"); + + Variant v44 = v4 < v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 < 1234567890 --> boolean"); + t.is(v44.get_bool(), false, "1234567890 < 1234567890 --> false"); + + Variant v45 = v4 < v5; + // 1234567890 corresponds to Fri Feb 13 06:31:30 PM EST 2009 hence 1200 + // (evaluated as now+1200s) be in future for any date after 2009-02-13 + t.is(v45.type(), Variant::type_boolean, "1234567890 < 1200 --> boolean"); + t.is(v45.get_bool(), true, "1234567890 < 1200 --> true"); + + Variant v50 = v5 < v0; + t.is(v50.type(), Variant::type_boolean, "1200 < true --> boolean"); + t.is(v50.get_bool(), false, "1200 < true --> false"); + + Variant v51 = v5 < v1; + t.is(v51.type(), Variant::type_boolean, "1200 < 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 < 42 --> false"); + + Variant v52 = v5 < v2; + t.is(v52.type(), Variant::type_boolean, "1200 < 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 < 3.14 --> false"); + + Variant v53 = v5 < v3; + t.is(v53.type(), Variant::type_boolean, "1200 < 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 < 'foo' --> false"); + + Variant v54 = v5 < v4; + // Same logic as v45. + t.is(v54.type(), Variant::type_boolean, "1200 < 1234567890 --> boolean"); + t.is(v54.get_bool(), false, "1200 < 1234567890 --> false"); + + Variant v55 = v5 < v5; + t.is(v55.type(), Variant::type_boolean, "1200 < 1200 --> boolean"); + t.is(v55.get_bool(), false, "1200 < 1200 --> false"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_lte.t.cpp b/test/variant_lte.t.cpp deleted file mode 100644 index b218ecdb9..000000000 --- a/test/variant_lte.t.cpp +++ /dev/null @@ -1,191 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_duration); - Variant v5 (1200, Variant::type_duration); - - Variant v00 = v0 <= v0; - t.is (v00.type (), Variant::type_boolean, "true <= true --> boolean"); - t.is (v00.get_bool (), true, "true <= true --> true"); - - Variant v01 = v0 <= v1; - t.is (v01.type (), Variant::type_boolean, "true <= 42 --> boolean"); - t.is (v01.get_bool (), true, "true <= 42 --> true"); - - Variant v02 = v0 <= v2; - t.is (v02.type (), Variant::type_boolean, "true <= 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true <= 3.14 --> true"); - - Variant v03 = v0 <= v3; - t.is (v03.type (), Variant::type_boolean, "true <= 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true <= 'foo' --> false"); - - Variant v04 = v0 <= v4; - t.is (v04.type (), Variant::type_boolean, "true <= 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true <= 1234567890 --> true"); - - Variant v05 = v0 <= v5; - t.is (v05.type (), Variant::type_boolean, "true <= 1200 --> boolean"); - t.is (v05.get_bool (), true, "true <= 1200 --> true"); - - Variant v10 = v1 <= v0; - t.is (v10.type (), Variant::type_boolean, "42 <= true --> boolean"); - t.is (v10.get_bool (), false, "42 <= true --> false"); - - Variant v11 = v1 <= v1; - t.is (v11.type (), Variant::type_boolean, "42 <= 42 --> boolean"); - t.is (v11.get_bool (), true, "42 <= 42 --> true"); - - Variant v12 = v1 <= v2; - t.is (v12.type (), Variant::type_boolean, "42 <= 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 <= 3.14 --> false"); - - Variant v13 = v1 <= v3; - t.is (v13.type (), Variant::type_boolean, "42 <= 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 <= 'foo' --> true"); - - Variant v14 = v1 <= v4; - t.is (v04.type (), Variant::type_boolean, "42 <= 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 <= 1234567890 --> true"); - - Variant v15 = v1 <= v5; - t.is (v15.type (), Variant::type_boolean, "42 <= 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 <= 1200 --> true"); - - Variant v20 = v2 <= v0; - t.is (v20.type (), Variant::type_boolean, "3.14 <= true --> boolean"); - t.is (v20.get_bool (), false, "3.14 <= true --> false"); - - Variant v21 = v2 <= v1; - t.is (v21.type (), Variant::type_boolean, "3.14 <= 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 <= 42 --> true"); - - Variant v22 = v2 <= v2; - t.is (v22.type (), Variant::type_boolean, "3.14 <= 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 <= 3.14 --> true"); - - Variant v23 = v2 <= v3; - t.is (v23.type (), Variant::type_boolean, "3.14 <= 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 <= 'foo' --> true"); - - Variant v24 = v2 <= v4; - t.is (v24.type (), Variant::type_boolean, "3.14 <= 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 <= 1234567890 --> true"); - - Variant v25 = v2 <= v5; - t.is (v25.type (), Variant::type_boolean, "3.14 <= 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 <= 1200 --> true"); - - Variant v30 = v3 <= v0; - t.is (v30.type (), Variant::type_boolean, "'foo' <= true --> boolean"); - t.is (v30.get_bool (), true, "'foo' <= true --> true"); - - Variant v31 = v3 <= v1; - t.is (v31.type (), Variant::type_boolean, "'foo' <= 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' <= 42 --> false"); - - Variant v32 = v3 <= v2; - t.is (v32.type (), Variant::type_boolean, "'foo' <= 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' <= 3.14 --> false"); - - Variant v33 = v3 <= v3; - t.is (v33.type (), Variant::type_boolean, "'foo' <= 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' <= 'foo' --> true"); - - Variant v34 = v3 <= v4; - t.is (v34.type (), Variant::type_boolean, "'foo' <= 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' <= 1234567890 --> true"); - - Variant v35 = v3 <= v5; - t.is (v35.type (), Variant::type_boolean, "'foo' <= 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' <= 1200 --> true"); - - Variant v40 = v4 <= v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 <= true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 <= true --> false"); - - Variant v41 = v4 <= v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 <= 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 <= 42 --> false"); - - Variant v42 = v4 <= v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 <= 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 <= 3.14 --> false"); - - Variant v43 = v4 <= v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 <= 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 <= 'foo' --> false"); - - Variant v44 = v4 <= v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 <= 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 <= 1234567890 --> true"); - - Variant v45 = v4 <= v5; - t.is (v45.type (), Variant::type_boolean, "1234567890 <= 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 <= 1200 --> false"); - - Variant v50 = v5 <= v0; - t.is (v50.type (), Variant::type_boolean, "1200 <= true --> boolean"); - t.is (v50.get_bool (), false, "1200 <= true --> false"); - - Variant v51 = v5 <= v1; - t.is (v51.type (), Variant::type_boolean, "1200 <= 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 <= 42 --> false"); - - Variant v52 = v5 <= v2; - t.is (v52.type (), Variant::type_boolean, "1200 <= 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 <= 3.14 --> false"); - - Variant v53 = v5 <= v3; - t.is (v53.type (), Variant::type_boolean, "1200 <= 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 <= 'foo' --> false"); - - Variant v54 = v5 <= v4; - t.is (v54.type (), Variant::type_boolean, "1200 <= 1234567890 --> boolean"); - t.is (v54.get_bool (), true, "1200 <= 1234567890 --> true"); - - Variant v55 = v5 <= v5; - t.is (v55.type (), Variant::type_boolean, "1200 <= 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 <= 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_lte_test.cpp b/test/variant_lte_test.cpp new file mode 100644 index 000000000..f68d4bf8a --- /dev/null +++ b/test/variant_lte_test.cpp @@ -0,0 +1,193 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_duration); + Variant v5(1200, Variant::type_duration); + + Variant v00 = v0 <= v0; + t.is(v00.type(), Variant::type_boolean, "true <= true --> boolean"); + t.is(v00.get_bool(), true, "true <= true --> true"); + + Variant v01 = v0 <= v1; + t.is(v01.type(), Variant::type_boolean, "true <= 42 --> boolean"); + t.is(v01.get_bool(), true, "true <= 42 --> true"); + + Variant v02 = v0 <= v2; + t.is(v02.type(), Variant::type_boolean, "true <= 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true <= 3.14 --> true"); + + Variant v03 = v0 <= v3; + t.is(v03.type(), Variant::type_boolean, "true <= 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true <= 'foo' --> false"); + + Variant v04 = v0 <= v4; + t.is(v04.type(), Variant::type_boolean, "true <= 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true <= 1234567890 --> true"); + + Variant v05 = v0 <= v5; + t.is(v05.type(), Variant::type_boolean, "true <= 1200 --> boolean"); + t.is(v05.get_bool(), true, "true <= 1200 --> true"); + + Variant v10 = v1 <= v0; + t.is(v10.type(), Variant::type_boolean, "42 <= true --> boolean"); + t.is(v10.get_bool(), false, "42 <= true --> false"); + + Variant v11 = v1 <= v1; + t.is(v11.type(), Variant::type_boolean, "42 <= 42 --> boolean"); + t.is(v11.get_bool(), true, "42 <= 42 --> true"); + + Variant v12 = v1 <= v2; + t.is(v12.type(), Variant::type_boolean, "42 <= 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 <= 3.14 --> false"); + + Variant v13 = v1 <= v3; + t.is(v13.type(), Variant::type_boolean, "42 <= 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 <= 'foo' --> true"); + + Variant v14 = v1 <= v4; + t.is(v04.type(), Variant::type_boolean, "42 <= 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 <= 1234567890 --> true"); + + Variant v15 = v1 <= v5; + t.is(v15.type(), Variant::type_boolean, "42 <= 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 <= 1200 --> true"); + + Variant v20 = v2 <= v0; + t.is(v20.type(), Variant::type_boolean, "3.14 <= true --> boolean"); + t.is(v20.get_bool(), false, "3.14 <= true --> false"); + + Variant v21 = v2 <= v1; + t.is(v21.type(), Variant::type_boolean, "3.14 <= 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 <= 42 --> true"); + + Variant v22 = v2 <= v2; + t.is(v22.type(), Variant::type_boolean, "3.14 <= 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 <= 3.14 --> true"); + + Variant v23 = v2 <= v3; + t.is(v23.type(), Variant::type_boolean, "3.14 <= 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 <= 'foo' --> true"); + + Variant v24 = v2 <= v4; + t.is(v24.type(), Variant::type_boolean, "3.14 <= 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 <= 1234567890 --> true"); + + Variant v25 = v2 <= v5; + t.is(v25.type(), Variant::type_boolean, "3.14 <= 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 <= 1200 --> true"); + + Variant v30 = v3 <= v0; + t.is(v30.type(), Variant::type_boolean, "'foo' <= true --> boolean"); + t.is(v30.get_bool(), true, "'foo' <= true --> true"); + + Variant v31 = v3 <= v1; + t.is(v31.type(), Variant::type_boolean, "'foo' <= 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' <= 42 --> false"); + + Variant v32 = v3 <= v2; + t.is(v32.type(), Variant::type_boolean, "'foo' <= 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' <= 3.14 --> false"); + + Variant v33 = v3 <= v3; + t.is(v33.type(), Variant::type_boolean, "'foo' <= 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' <= 'foo' --> true"); + + Variant v34 = v3 <= v4; + t.is(v34.type(), Variant::type_boolean, "'foo' <= 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' <= 1234567890 --> true"); + + Variant v35 = v3 <= v5; + t.is(v35.type(), Variant::type_boolean, "'foo' <= 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' <= 1200 --> true"); + + Variant v40 = v4 <= v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 <= true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 <= true --> false"); + + Variant v41 = v4 <= v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 <= 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 <= 42 --> false"); + + Variant v42 = v4 <= v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 <= 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 <= 3.14 --> false"); + + Variant v43 = v4 <= v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 <= 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 <= 'foo' --> false"); + + Variant v44 = v4 <= v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 <= 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 <= 1234567890 --> true"); + + Variant v45 = v4 <= v5; + t.is(v45.type(), Variant::type_boolean, "1234567890 <= 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 <= 1200 --> false"); + + Variant v50 = v5 <= v0; + t.is(v50.type(), Variant::type_boolean, "1200 <= true --> boolean"); + t.is(v50.get_bool(), false, "1200 <= true --> false"); + + Variant v51 = v5 <= v1; + t.is(v51.type(), Variant::type_boolean, "1200 <= 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 <= 42 --> false"); + + Variant v52 = v5 <= v2; + t.is(v52.type(), Variant::type_boolean, "1200 <= 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 <= 3.14 --> false"); + + Variant v53 = v5 <= v3; + t.is(v53.type(), Variant::type_boolean, "1200 <= 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 <= 'foo' --> false"); + + Variant v54 = v5 <= v4; + t.is(v54.type(), Variant::type_boolean, "1200 <= 1234567890 --> boolean"); + t.is(v54.get_bool(), true, "1200 <= 1234567890 --> true"); + + Variant v55 = v5 <= v5; + t.is(v55.type(), Variant::type_boolean, "1200 <= 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 <= 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_match.t.cpp b/test/variant_match.t.cpp deleted file mode 100644 index 5b39e62dc..000000000 --- a/test/variant_match.t.cpp +++ /dev/null @@ -1,297 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -Task task; - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (120); - - Variant vs0 ("untrue"); // ~ true - Variant vs1 (8421); // ~ 42 - Variant vs2 (3.14159); // ~ 3.14 - Variant vs3 ("foolish"); // ~ foo - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // Interesting cases. - Variant vs00 = vs0.operator_match (v0, task); - t.is (vs00.type (), Variant::type_boolean, "untrue ~ true --> boolean"); - t.is (vs00.get_bool (), true, "untrue ~ true --> true"); - - Variant vs01 = vs0.operator_match (v1, task); - t.is (vs01.type (), Variant::type_boolean, "untrue ~ 42 --> boolean"); - t.is (vs01.get_bool (), false, "untrue ~ 42 --> false"); - - Variant vs02 = vs0.operator_match (v2, task); - t.is (vs02.type (), Variant::type_boolean, "untrue ~ 3.14 --> boolean"); - t.is (vs02.get_bool (), false, "untrue ~ 3.14 --> false"); - - Variant vs03 = vs0.operator_match (v3, task); - t.is (vs03.type (), Variant::type_boolean, "untrue ~ 'foo' --> boolean"); - t.is (vs03.get_bool (), false, "untrue ~ 'foo' --> false"); - - Variant vs04 = vs0.operator_match (v4, task); - t.is (vs04.type (), Variant::type_boolean, "untrue ~ 1234567890 --> boolean"); - t.is (vs04.get_bool (), false, "untrue ~ 1234567890 --> false"); - - Variant vs05 = vs0.operator_match (v5, task); - t.is (vs05.type (), Variant::type_boolean, "untrue ~ 1200 --> boolean"); - t.is (vs05.get_bool (), false, "untrue ~ 1200 --> false"); - - Variant vs10 = vs1.operator_match (v0, task); - t.is (vs10.type (), Variant::type_boolean, "8421 ~ true --> boolean"); - t.is (vs10.get_bool (), false, "8421 ~ true --> false"); - - Variant vs11 = vs1.operator_match (v1, task); - t.is (vs11.type (), Variant::type_boolean, "8421 ~ 42 --> boolean"); - t.is (vs11.get_bool (), true, "8421 ~ 42 --> true"); - - Variant vs12 = vs1.operator_match (v2, task); - t.is (vs12.type (), Variant::type_boolean, "8421 ~ 3.14 --> boolean"); - t.is (vs12.get_bool (), false, "8421 ~ 3.14 --> false"); - - Variant vs13 = vs1.operator_match (v3, task); - t.is (vs13.type (), Variant::type_boolean, "8421 ~ 'foo' --> boolean"); - t.is (vs13.get_bool (), false, "8421 ~ 'foo' --> false"); - - Variant vs14 = vs1.operator_match (v4, task); - t.is (vs14.type (), Variant::type_boolean, "8421 ~ 1234567890 --> boolean"); - t.is (vs14.get_bool (), false, "8421 ~ 1234567890 --> false"); - - Variant vs15 = vs1.operator_match (v5, task); - t.is (vs15.type (), Variant::type_boolean, "8421 ~ 1200 --> boolean"); - t.is (vs15.get_bool (), false, "8421 ~ 1200 --> false"); - - Variant vs20 = vs2.operator_match (v0, task); - t.is (vs20.type (), Variant::type_boolean, "3.14159 ~ true --> boolean"); - t.is (vs20.get_bool (), false, "3.14159 ~ true --> false"); - - Variant vs21 = vs2.operator_match (v1, task); - t.is (vs21.type (), Variant::type_boolean, "3.14159 ~ 42 --> boolean"); - t.is (vs21.get_bool (), false, "3.14159 ~ 42 --> false"); - - Variant vs22 = vs2.operator_match (v2, task); - t.is (vs22.type (), Variant::type_boolean, "3.14159 ~ 3.14 --> boolean"); - t.is (vs22.get_bool (), true, "3.14159 ~ 3.14 --> true"); - - Variant vs23 = vs2.operator_match (v3, task); - t.is (vs23.type (), Variant::type_boolean, "3.14159 ~ 'foo' --> boolean"); - t.is (vs23.get_bool (), false, "3.14159 ~ 'foo' --> false"); - - Variant vs24 = vs2.operator_match (v4, task); - t.is (vs24.type (), Variant::type_boolean, "3.14159 ~ 1234567890 --> boolean"); - t.is (vs24.get_bool (), false, "3.14159 ~ 1234567890 --> false"); - - Variant vs25 = vs2.operator_match (v5, task); - t.is (vs25.type (), Variant::type_boolean, "3.14159 ~ 1200 --> boolean"); - t.is (vs25.get_bool (), false, "3.14159 ~ 1200 --> false"); - - Variant vs30 = vs3.operator_match (v0, task); - t.is (vs30.type (), Variant::type_boolean, "foolish ~ true --> boolean"); - t.is (vs30.get_bool (), false, "foolish ~ true --> false"); - - Variant vs31 = vs3.operator_match (v1, task); - t.is (vs31.type (), Variant::type_boolean, "foolish ~ 42 --> boolean"); - t.is (vs31.get_bool (), false, "foolish ~ 42 --> false"); - - Variant vs32 = vs3.operator_match (v2, task); - t.is (vs32.type (), Variant::type_boolean, "foolish ~ 3.14 --> boolean"); - t.is (vs32.get_bool (), false, "foolish ~ 3.14 --> false"); - - Variant vs33 = vs3.operator_match (v3, task); - t.is (vs33.type (), Variant::type_boolean, "foolish ~ 'foo' --> boolean"); - t.is (vs33.get_bool (), true, "foolish ~ 'foo' --> true"); - - Variant vs34 = vs3.operator_match (v4, task); - t.is (vs34.type (), Variant::type_boolean, "foolish ~ 1234567890 --> boolean"); - t.is (vs34.get_bool (), false, "foolish ~ 1234567890 --> false"); - - Variant vs35 = vs3.operator_match (v5, task); - t.is (vs35.type (), Variant::type_boolean, "foolish ~ 1200 --> boolean"); - t.is (vs35.get_bool (), false, "foolish ~ 1200 --> false"); - - // Exhaustive comparisons. - Variant v00 = v0.operator_match (v0, task); - t.is (v00.type (), Variant::type_boolean, "true ~ true --> boolean"); - t.is (v00.get_bool (), true, "true ~ true --> true"); - - Variant v01 = v0.operator_match (v1, task); - t.is (v01.type (), Variant::type_boolean, "true ~ 42 --> boolean"); - t.is (v01.get_bool (), false, "true ~ 42 --> false"); - - Variant v02 = v0.operator_match (v2, task); - t.is (v02.type (), Variant::type_boolean, "true ~ 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true ~ 3.14 --> false"); - - Variant v03 = v0.operator_match (v3, task); - t.is (v03.type (), Variant::type_boolean, "true ~ 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true ~ 'foo' --> false"); - - Variant v04 = v0.operator_match (v4, task); - t.is (v04.type (), Variant::type_boolean, "true ~ 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true ~ 1234567890 --> false"); - - Variant v05 = v0.operator_match (v5, task); - t.is (v05.type (), Variant::type_boolean, "true ~ 1200 --> boolean"); - t.is (v05.get_bool (), false, "true ~ 1200 --> false"); - - Variant v10 = v1.operator_match (v0, task); - t.is (v10.type (), Variant::type_boolean, "42 ~ true --> boolean"); - t.is (v10.get_bool (), false, "42 ~ true --> false"); - - Variant v11 = v1.operator_match (v1, task); - t.is (v11.type (), Variant::type_boolean, "42 ~ 42 --> boolean"); - t.is (v11.get_bool (), true, "42 ~ 42 --> true"); - - Variant v12 = v1.operator_match (v2, task); - t.is (v12.type (), Variant::type_boolean, "42 ~ 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 ~ 3.14 --> false"); - - Variant v13 = v1.operator_match (v3, task); - t.is (v13.type (), Variant::type_boolean, "42 ~ 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 ~ 'foo' --> false"); - - Variant v14 = v1.operator_match (v4, task); - t.is (v04.type (), Variant::type_boolean, "42 ~ 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 ~ 1234567890 --> false"); - - Variant v15 = v1.operator_match (v5, task); - t.is (v15.type (), Variant::type_boolean, "42 ~ 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 ~ 1200 --> false"); - - Variant v20 = v2.operator_match (v0, task); - t.is (v20.type (), Variant::type_boolean, "3.14 ~ true --> boolean"); - t.is (v20.get_bool (), false, "3.14 ~ true --> false"); - - Variant v21 = v2.operator_match (v1, task); - t.is (v21.type (), Variant::type_boolean, "3.14 ~ 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 ~ 42 --> false"); - - Variant v22 = v2.operator_match (v2, task); - t.is (v22.type (), Variant::type_boolean, "3.14 ~ 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 ~ 3.14 --> true"); - - Variant v23 = v2.operator_match (v3, task); - t.is (v23.type (), Variant::type_boolean, "3.14 ~ 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 ~ 'foo' --> false"); - - Variant v24 = v2.operator_match (v4, task); - t.is (v24.type (), Variant::type_boolean, "3.14 ~ 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 ~ 1234567890 --> false"); - - Variant v25 = v2.operator_match (v5, task); - t.is (v25.type (), Variant::type_boolean, "3.14 ~ 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 ~ 1200 --> false"); - - Variant v30 = v3.operator_match (v0, task); - t.is (v30.type (), Variant::type_boolean, "'foo' ~ true --> boolean"); - t.is (v30.get_bool (), false, "'foo' ~ true --> false"); - - Variant v31 = v3.operator_match (v1, task); - t.is (v31.type (), Variant::type_boolean, "'foo' ~ 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' ~ 42 --> false"); - - Variant v32 = v3.operator_match (v2, task); - t.is (v32.type (), Variant::type_boolean, "'foo' ~ 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' ~ 3.14 --> false"); - - Variant v33 = v3.operator_match (v3, task); - t.is (v33.type (), Variant::type_boolean, "'foo' ~ 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' ~ 'foo' --> true"); - - Variant v34 = v3.operator_match (v4, task); - t.is (v34.type (), Variant::type_boolean, "'foo' ~ 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' ~ 1234567890 --> false"); - - Variant v35 = v3.operator_match (v5, task); - t.is (v35.type (), Variant::type_boolean, "'foo' ~ 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' ~ 1200 --> false"); - - Variant v40 = v4.operator_match (v0, task); - t.is (v40.type (), Variant::type_boolean, "1234567890 ~ true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 ~ true --> false"); - - Variant v41 = v4.operator_match (v1, task); - t.is (v41.type (), Variant::type_boolean, "1234567890 ~ 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 ~ 42 --> false"); - - Variant v42 = v4.operator_match (v2, task); - t.is (v42.type (), Variant::type_boolean, "1234567890 ~ 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 ~ 3.14 --> false"); - - Variant v43 = v4.operator_match (v3, task); - t.is (v43.type (), Variant::type_boolean, "1234567890 ~ 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 ~ 'foo' --> false"); - - Variant v44 = v4.operator_match (v4, task); - t.is (v44.type (), Variant::type_boolean, "1234567890 ~ 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 ~ 1234567890 --> true"); - - Variant v45 = v4.operator_match (v5, task); - t.is (v45.type (), Variant::type_boolean, "1234567890 ~ 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 ~ 1200 --> false"); - - Variant v50 = v5.operator_match (v0, task); - t.is (v50.type (), Variant::type_boolean, "1200 ~ true --> boolean"); - t.is (v50.get_bool (), false, "1200 ~ true --> false"); - - Variant v51 = v5.operator_match (v1, task); - t.is (v51.type (), Variant::type_boolean, "1200 ~ 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 ~ 42 --> false"); - - Variant v52 = v5.operator_match (v2, task); - t.is (v52.type (), Variant::type_boolean, "1200 ~ 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 ~ 3.14 --> false"); - - Variant v53 = v5.operator_match (v3, task); - t.is (v53.type (), Variant::type_boolean, "1200 ~ 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 ~ 'foo' --> false"); - - Variant v54 = v5.operator_match (v4, task); - t.is (v04.type (), Variant::type_boolean, "1200 ~ 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "1200 ~ 1234567890 --> false"); - - Variant v55 = v5.operator_match (v5, task); - t.is (v55.type (), Variant::type_boolean, "1200 ~ 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 ~ 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_match_test.cpp b/test/variant_match_test.cpp new file mode 100644 index 000000000..879adc8d3 --- /dev/null +++ b/test/variant_match_test.cpp @@ -0,0 +1,299 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(120); + + Task task; + + Variant vs0("untrue"); // ~ true + Variant vs1(8421); // ~ 42 + Variant vs2(3.14159); // ~ 3.14 + Variant vs3("foolish"); // ~ foo + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // Interesting cases. + Variant vs00 = vs0.operator_match(v0, task); + t.is(vs00.type(), Variant::type_boolean, "untrue ~ true --> boolean"); + t.is(vs00.get_bool(), true, "untrue ~ true --> true"); + + Variant vs01 = vs0.operator_match(v1, task); + t.is(vs01.type(), Variant::type_boolean, "untrue ~ 42 --> boolean"); + t.is(vs01.get_bool(), false, "untrue ~ 42 --> false"); + + Variant vs02 = vs0.operator_match(v2, task); + t.is(vs02.type(), Variant::type_boolean, "untrue ~ 3.14 --> boolean"); + t.is(vs02.get_bool(), false, "untrue ~ 3.14 --> false"); + + Variant vs03 = vs0.operator_match(v3, task); + t.is(vs03.type(), Variant::type_boolean, "untrue ~ 'foo' --> boolean"); + t.is(vs03.get_bool(), false, "untrue ~ 'foo' --> false"); + + Variant vs04 = vs0.operator_match(v4, task); + t.is(vs04.type(), Variant::type_boolean, "untrue ~ 1234567890 --> boolean"); + t.is(vs04.get_bool(), false, "untrue ~ 1234567890 --> false"); + + Variant vs05 = vs0.operator_match(v5, task); + t.is(vs05.type(), Variant::type_boolean, "untrue ~ 1200 --> boolean"); + t.is(vs05.get_bool(), false, "untrue ~ 1200 --> false"); + + Variant vs10 = vs1.operator_match(v0, task); + t.is(vs10.type(), Variant::type_boolean, "8421 ~ true --> boolean"); + t.is(vs10.get_bool(), false, "8421 ~ true --> false"); + + Variant vs11 = vs1.operator_match(v1, task); + t.is(vs11.type(), Variant::type_boolean, "8421 ~ 42 --> boolean"); + t.is(vs11.get_bool(), true, "8421 ~ 42 --> true"); + + Variant vs12 = vs1.operator_match(v2, task); + t.is(vs12.type(), Variant::type_boolean, "8421 ~ 3.14 --> boolean"); + t.is(vs12.get_bool(), false, "8421 ~ 3.14 --> false"); + + Variant vs13 = vs1.operator_match(v3, task); + t.is(vs13.type(), Variant::type_boolean, "8421 ~ 'foo' --> boolean"); + t.is(vs13.get_bool(), false, "8421 ~ 'foo' --> false"); + + Variant vs14 = vs1.operator_match(v4, task); + t.is(vs14.type(), Variant::type_boolean, "8421 ~ 1234567890 --> boolean"); + t.is(vs14.get_bool(), false, "8421 ~ 1234567890 --> false"); + + Variant vs15 = vs1.operator_match(v5, task); + t.is(vs15.type(), Variant::type_boolean, "8421 ~ 1200 --> boolean"); + t.is(vs15.get_bool(), false, "8421 ~ 1200 --> false"); + + Variant vs20 = vs2.operator_match(v0, task); + t.is(vs20.type(), Variant::type_boolean, "3.14159 ~ true --> boolean"); + t.is(vs20.get_bool(), false, "3.14159 ~ true --> false"); + + Variant vs21 = vs2.operator_match(v1, task); + t.is(vs21.type(), Variant::type_boolean, "3.14159 ~ 42 --> boolean"); + t.is(vs21.get_bool(), false, "3.14159 ~ 42 --> false"); + + Variant vs22 = vs2.operator_match(v2, task); + t.is(vs22.type(), Variant::type_boolean, "3.14159 ~ 3.14 --> boolean"); + t.is(vs22.get_bool(), true, "3.14159 ~ 3.14 --> true"); + + Variant vs23 = vs2.operator_match(v3, task); + t.is(vs23.type(), Variant::type_boolean, "3.14159 ~ 'foo' --> boolean"); + t.is(vs23.get_bool(), false, "3.14159 ~ 'foo' --> false"); + + Variant vs24 = vs2.operator_match(v4, task); + t.is(vs24.type(), Variant::type_boolean, "3.14159 ~ 1234567890 --> boolean"); + t.is(vs24.get_bool(), false, "3.14159 ~ 1234567890 --> false"); + + Variant vs25 = vs2.operator_match(v5, task); + t.is(vs25.type(), Variant::type_boolean, "3.14159 ~ 1200 --> boolean"); + t.is(vs25.get_bool(), false, "3.14159 ~ 1200 --> false"); + + Variant vs30 = vs3.operator_match(v0, task); + t.is(vs30.type(), Variant::type_boolean, "foolish ~ true --> boolean"); + t.is(vs30.get_bool(), false, "foolish ~ true --> false"); + + Variant vs31 = vs3.operator_match(v1, task); + t.is(vs31.type(), Variant::type_boolean, "foolish ~ 42 --> boolean"); + t.is(vs31.get_bool(), false, "foolish ~ 42 --> false"); + + Variant vs32 = vs3.operator_match(v2, task); + t.is(vs32.type(), Variant::type_boolean, "foolish ~ 3.14 --> boolean"); + t.is(vs32.get_bool(), false, "foolish ~ 3.14 --> false"); + + Variant vs33 = vs3.operator_match(v3, task); + t.is(vs33.type(), Variant::type_boolean, "foolish ~ 'foo' --> boolean"); + t.is(vs33.get_bool(), true, "foolish ~ 'foo' --> true"); + + Variant vs34 = vs3.operator_match(v4, task); + t.is(vs34.type(), Variant::type_boolean, "foolish ~ 1234567890 --> boolean"); + t.is(vs34.get_bool(), false, "foolish ~ 1234567890 --> false"); + + Variant vs35 = vs3.operator_match(v5, task); + t.is(vs35.type(), Variant::type_boolean, "foolish ~ 1200 --> boolean"); + t.is(vs35.get_bool(), false, "foolish ~ 1200 --> false"); + + // Exhaustive comparisons. + Variant v00 = v0.operator_match(v0, task); + t.is(v00.type(), Variant::type_boolean, "true ~ true --> boolean"); + t.is(v00.get_bool(), true, "true ~ true --> true"); + + Variant v01 = v0.operator_match(v1, task); + t.is(v01.type(), Variant::type_boolean, "true ~ 42 --> boolean"); + t.is(v01.get_bool(), false, "true ~ 42 --> false"); + + Variant v02 = v0.operator_match(v2, task); + t.is(v02.type(), Variant::type_boolean, "true ~ 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true ~ 3.14 --> false"); + + Variant v03 = v0.operator_match(v3, task); + t.is(v03.type(), Variant::type_boolean, "true ~ 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true ~ 'foo' --> false"); + + Variant v04 = v0.operator_match(v4, task); + t.is(v04.type(), Variant::type_boolean, "true ~ 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true ~ 1234567890 --> false"); + + Variant v05 = v0.operator_match(v5, task); + t.is(v05.type(), Variant::type_boolean, "true ~ 1200 --> boolean"); + t.is(v05.get_bool(), false, "true ~ 1200 --> false"); + + Variant v10 = v1.operator_match(v0, task); + t.is(v10.type(), Variant::type_boolean, "42 ~ true --> boolean"); + t.is(v10.get_bool(), false, "42 ~ true --> false"); + + Variant v11 = v1.operator_match(v1, task); + t.is(v11.type(), Variant::type_boolean, "42 ~ 42 --> boolean"); + t.is(v11.get_bool(), true, "42 ~ 42 --> true"); + + Variant v12 = v1.operator_match(v2, task); + t.is(v12.type(), Variant::type_boolean, "42 ~ 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 ~ 3.14 --> false"); + + Variant v13 = v1.operator_match(v3, task); + t.is(v13.type(), Variant::type_boolean, "42 ~ 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 ~ 'foo' --> false"); + + Variant v14 = v1.operator_match(v4, task); + t.is(v04.type(), Variant::type_boolean, "42 ~ 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 ~ 1234567890 --> false"); + + Variant v15 = v1.operator_match(v5, task); + t.is(v15.type(), Variant::type_boolean, "42 ~ 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 ~ 1200 --> false"); + + Variant v20 = v2.operator_match(v0, task); + t.is(v20.type(), Variant::type_boolean, "3.14 ~ true --> boolean"); + t.is(v20.get_bool(), false, "3.14 ~ true --> false"); + + Variant v21 = v2.operator_match(v1, task); + t.is(v21.type(), Variant::type_boolean, "3.14 ~ 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 ~ 42 --> false"); + + Variant v22 = v2.operator_match(v2, task); + t.is(v22.type(), Variant::type_boolean, "3.14 ~ 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 ~ 3.14 --> true"); + + Variant v23 = v2.operator_match(v3, task); + t.is(v23.type(), Variant::type_boolean, "3.14 ~ 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 ~ 'foo' --> false"); + + Variant v24 = v2.operator_match(v4, task); + t.is(v24.type(), Variant::type_boolean, "3.14 ~ 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 ~ 1234567890 --> false"); + + Variant v25 = v2.operator_match(v5, task); + t.is(v25.type(), Variant::type_boolean, "3.14 ~ 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 ~ 1200 --> false"); + + Variant v30 = v3.operator_match(v0, task); + t.is(v30.type(), Variant::type_boolean, "'foo' ~ true --> boolean"); + t.is(v30.get_bool(), false, "'foo' ~ true --> false"); + + Variant v31 = v3.operator_match(v1, task); + t.is(v31.type(), Variant::type_boolean, "'foo' ~ 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' ~ 42 --> false"); + + Variant v32 = v3.operator_match(v2, task); + t.is(v32.type(), Variant::type_boolean, "'foo' ~ 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' ~ 3.14 --> false"); + + Variant v33 = v3.operator_match(v3, task); + t.is(v33.type(), Variant::type_boolean, "'foo' ~ 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' ~ 'foo' --> true"); + + Variant v34 = v3.operator_match(v4, task); + t.is(v34.type(), Variant::type_boolean, "'foo' ~ 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' ~ 1234567890 --> false"); + + Variant v35 = v3.operator_match(v5, task); + t.is(v35.type(), Variant::type_boolean, "'foo' ~ 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' ~ 1200 --> false"); + + Variant v40 = v4.operator_match(v0, task); + t.is(v40.type(), Variant::type_boolean, "1234567890 ~ true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 ~ true --> false"); + + Variant v41 = v4.operator_match(v1, task); + t.is(v41.type(), Variant::type_boolean, "1234567890 ~ 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 ~ 42 --> false"); + + Variant v42 = v4.operator_match(v2, task); + t.is(v42.type(), Variant::type_boolean, "1234567890 ~ 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 ~ 3.14 --> false"); + + Variant v43 = v4.operator_match(v3, task); + t.is(v43.type(), Variant::type_boolean, "1234567890 ~ 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 ~ 'foo' --> false"); + + Variant v44 = v4.operator_match(v4, task); + t.is(v44.type(), Variant::type_boolean, "1234567890 ~ 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 ~ 1234567890 --> true"); + + Variant v45 = v4.operator_match(v5, task); + t.is(v45.type(), Variant::type_boolean, "1234567890 ~ 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 ~ 1200 --> false"); + + Variant v50 = v5.operator_match(v0, task); + t.is(v50.type(), Variant::type_boolean, "1200 ~ true --> boolean"); + t.is(v50.get_bool(), false, "1200 ~ true --> false"); + + Variant v51 = v5.operator_match(v1, task); + t.is(v51.type(), Variant::type_boolean, "1200 ~ 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 ~ 42 --> false"); + + Variant v52 = v5.operator_match(v2, task); + t.is(v52.type(), Variant::type_boolean, "1200 ~ 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 ~ 3.14 --> false"); + + Variant v53 = v5.operator_match(v3, task); + t.is(v53.type(), Variant::type_boolean, "1200 ~ 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 ~ 'foo' --> false"); + + Variant v54 = v5.operator_match(v4, task); + t.is(v04.type(), Variant::type_boolean, "1200 ~ 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "1200 ~ 1234567890 --> false"); + + Variant v55 = v5.operator_match(v5, task); + t.is(v55.type(), Variant::type_boolean, "1200 ~ 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 ~ 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_math.t.cpp b/test/variant_math_test.cpp similarity index 89% rename from test/variant_math.t.cpp rename to test/variant_math_test.cpp index b8a7b3a21..46b4dc10f 100644 --- a/test/variant_math.t.cpp +++ b/test/variant_math_test.cpp @@ -25,20 +25,22 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include +// cmake.h include header must come first + #include +#include + +#include #define EPSILON 0.001 //////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (1); +int TEST_NAME(int, char**) { + UnitTest t(1); - Variant v0 (10.0); - v0.sqrt (); - t.is (v0.get_real (), 3.1622, EPSILON, "math sqrt 10 -> 3.1622"); + Variant v0(10.0); + v0.sqrt(); + t.is(v0.get_real(), 3.1622, EPSILON, "math sqrt 10 -> 3.1622"); return 0; } diff --git a/test/variant_modulo.t.cpp b/test/variant_modulo.t.cpp deleted file mode 100644 index 75a9d2ad2..000000000 --- a/test/variant_modulo.t.cpp +++ /dev/null @@ -1,197 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021 Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.0001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (40); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean / boolean -> ERROR - try {Variant v00 = v0 % v0; t.fail ("true % true --> error");} - catch (...) {t.pass ("true % true --> error");} - - // boolean / integer -> ERROR - try {Variant v01 = v0 % v1; t.fail ("true % 42 --> error");} - catch (...) {t.pass ("true % 42 --> error");} - - // boolean / real -> ERROR - try {Variant v02 = v0 % v2; t.fail ("true % 3.14 --> error");} - catch (...) {t.pass ("true % 3.14 --> error");} - - // boolean / string -> ERROR - try {Variant v03 = v0 % v3; t.fail ("true % foo --> error");} - catch (...) {t.pass ("true % foo --> error");} - - // boolean / date -> ERROR - try {Variant v04 = v0 % v4; t.fail ("true % 1234567890 --> error");} - catch (...) {t.pass ("true % 1234567890 --> error");} - - // boolean / duration -> ERROR - try {Variant v05 = v0 % v5; t.fail ("true % 1200 --> error");} - catch (...) {t.pass ("true % 1200 --> error");} - - // integer / boolean -> ERROR - try {Variant v10 = v1 % v0; t.fail ("42 % true --> error");} - catch (...) {t.pass ("42 % true --> error");} - - // integer / integer -> integer - Variant v11 = v1 % v1; - t.is (v11.type (), Variant::type_integer, "42 % 42 --> integer"); - t.is (v11.get_integer (), 0, "42 % 42 --> 0"); - - // integer / real -> real - Variant v12 = v1 % v2; - t.is (v12.type (), Variant::type_real, "42 % 3.14 --> real"); - t.is (v12.get_real (), 1.18, EPSILON, "42 % 3.14 --> 1.18"); - - // integer / string -> ERROR - try {Variant v13 = v1 % v3; t.fail ("42 % foo --> error");} - catch (...) {t.pass ("42 % foo --> error");} - - // integer / date -> ERROR - try {Variant v14 = v1 % v4; t.fail ("42 % 1234567890 --> error");} - catch (...) {t.pass ("42 % 1234567890 --> error");} - - // integer / duration -> duration - try {Variant v15 = v1 % v5; t.fail ("42 % 1200 --> error");} - catch (...) {t.pass ("42 % 1200 --> error");} - - // real / boolean -> ERROR - try {Variant v20 = v2 % v0; t.fail ("3.14 % true --> error");} - catch (...) {t.pass ("3.14 % true --> error");} - - // real / integer -> real - Variant v21 = v2 % v1; - t.is (v21.type (), Variant::type_real, "3.14 % 42 --> real"); - t.is (v21.get_real (), 3.14, EPSILON, "3.14 % 42 --> 3.14"); - - // real / real -> real - Variant v22 = v2 % v2; - t.is (v22.type (), Variant::type_real, "3.14 % 3.14 --> real"); - t.is (v22.get_real (), 0.0, EPSILON, "3.14 % 3.14 --> 0.0"); - - // real / string -> error - try {Variant v23 = v2 % v3; t.fail ("3.14 % foo --> error");} - catch (...) {t.pass ("3.14 % foo --> error");} - - // real / date -> error - try {Variant v24 = v2 % v4; t.fail ("3.14 % 1234567890 --> error");} - catch (...) {t.pass ("3.14 % 1234567890 --> error");} - - // real / duration -> duration - try {Variant v25 = v2 % v5; t.fail ("3.14 % 1200 --> error");} - catch (...) {t.pass ("3.14 % 1200 --> error");} - - // string / boolean -> ERROR - try {Variant v30 = v3 % v0; t.fail ("foo % true --> error");} - catch (...) {t.pass ("foo % true --> error");} - - // string / integer -> ERROR - try {Variant v31 = v3 % v1; t.fail ("foo % 42 --> error");} - catch (...) {t.pass ("foo % 42 --> error");} - - // string / real -> ERROR - try {Variant v32 = v3 % v2; t.fail ("foo % 3.14 --> error");} - catch (...) {t.pass ("foo % 3.14 --> error");} - - // string / string -> ERROR - try {Variant v33 = v3 % v3; t.fail ("foo % foo --> error");} - catch (...) {t.pass ("foo % foo --> error");} - - // string / date -> ERROR - try {Variant v34 = v3 % v4; t.fail ("foo % 1234567890 --> error");} - catch (...) {t.pass ("foo % 1234567890 --> error");} - - // string / duration -> ERROR - try {Variant v35 = v3 % v5; t.fail ("foo % 1200 --> error");} - catch (...) {t.pass ("foo % 1200 --> error");} - - // date / boolean -> ERROR - try {Variant v40 = v4 % v0; t.fail ("1234567890 % true --> error");} - catch (...) {t.pass ("1234567890 % true --> error");} - - // date / integer -> ERROR - try {Variant v41 = v4 % v1; t.fail ("1234567890 % 42 --> error");} - catch (...) {t.pass ("1234567890 % 42 --> error");} - - // date / real -> ERROR - try {Variant v42 = v4 % v2; t.fail ("1234567890 % 3.14 --> error");} - catch (...) {t.pass ("1234567890 % 3.14 --> error");} - - // date / string -> ERROR - try {Variant v43 = v4 % v3; t.fail ("1234567890 % foo --> error");} - catch (...) {t.pass ("1234567890 % foo --> error");} - - // date / date -> ERROR - try {Variant v44 = v4 % v4; t.fail ("1234567890 % 1234567890 --> error");} - catch (...) {t.pass ("1234567890 % 1234567890 --> error");} - - // date / duration -> ERROR - try {Variant v45 = v4 % v5; t.fail ("1234567890 % 1200 --> error");} - catch (...) {t.pass ("1234567890 % 1200 --> error");} - - // duration / boolean -> ERROR - try {Variant v50 = v5 % v0; t.fail ("1200 % true --> error");} - catch (...) {t.pass ("1200 % true --> error");} - - // duration / integer -> ERROR - try {Variant v51 = v5 % v1; t.fail ("1200 % 42 --> error");} - catch (...) {t.pass ("1200 % 42 --> error");} - - // duration / real -> ERROR - try {Variant v52 = v5 % v2; t.fail ("1200 % 3.14 --> error");} - catch (...) {t.pass ("1200 % 3.14 --> error");} - - // duration / string -> ERROR - try {Variant v53 = v5 % v3; t.fail ("1200 % foo --> error");} - catch (...) {t.pass ("1200 % foo --> error");} - - // duration / date -> ERROR - try {Variant v54 = v5 % v4; t.fail ("1200 % 1234567890 --> error");} - catch (...) {t.pass ("1200 % 1234567890 --> error");} - - // duration / duration -> ERROR - try {Variant v55 = v5 % v5; t.fail ("1200 % 1200 --> error");} - catch (...) {t.pass ("1200 % 1200 --> error");} - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_modulo_test.cpp b/test/variant_modulo_test.cpp new file mode 100644 index 000000000..39ef6a2b6 --- /dev/null +++ b/test/variant_modulo_test.cpp @@ -0,0 +1,327 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021 Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.0001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(40); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean / boolean -> ERROR + try { + Variant v00 = v0 % v0; + t.fail("true % true --> error"); + } catch (...) { + t.pass("true % true --> error"); + } + + // boolean / integer -> ERROR + try { + Variant v01 = v0 % v1; + t.fail("true % 42 --> error"); + } catch (...) { + t.pass("true % 42 --> error"); + } + + // boolean / real -> ERROR + try { + Variant v02 = v0 % v2; + t.fail("true % 3.14 --> error"); + } catch (...) { + t.pass("true % 3.14 --> error"); + } + + // boolean / string -> ERROR + try { + Variant v03 = v0 % v3; + t.fail("true % foo --> error"); + } catch (...) { + t.pass("true % foo --> error"); + } + + // boolean / date -> ERROR + try { + Variant v04 = v0 % v4; + t.fail("true % 1234567890 --> error"); + } catch (...) { + t.pass("true % 1234567890 --> error"); + } + + // boolean / duration -> ERROR + try { + Variant v05 = v0 % v5; + t.fail("true % 1200 --> error"); + } catch (...) { + t.pass("true % 1200 --> error"); + } + + // integer / boolean -> ERROR + try { + Variant v10 = v1 % v0; + t.fail("42 % true --> error"); + } catch (...) { + t.pass("42 % true --> error"); + } + + // integer / integer -> integer + Variant v11 = v1 % v1; + t.is(v11.type(), Variant::type_integer, "42 % 42 --> integer"); + t.is(v11.get_integer(), 0, "42 % 42 --> 0"); + + // integer / real -> real + Variant v12 = v1 % v2; + t.is(v12.type(), Variant::type_real, "42 % 3.14 --> real"); + t.is(v12.get_real(), 1.18, EPSILON, "42 % 3.14 --> 1.18"); + + // integer / string -> ERROR + try { + Variant v13 = v1 % v3; + t.fail("42 % foo --> error"); + } catch (...) { + t.pass("42 % foo --> error"); + } + + // integer / date -> ERROR + try { + Variant v14 = v1 % v4; + t.fail("42 % 1234567890 --> error"); + } catch (...) { + t.pass("42 % 1234567890 --> error"); + } + + // integer / duration -> duration + try { + Variant v15 = v1 % v5; + t.fail("42 % 1200 --> error"); + } catch (...) { + t.pass("42 % 1200 --> error"); + } + + // real / boolean -> ERROR + try { + Variant v20 = v2 % v0; + t.fail("3.14 % true --> error"); + } catch (...) { + t.pass("3.14 % true --> error"); + } + + // real / integer -> real + Variant v21 = v2 % v1; + t.is(v21.type(), Variant::type_real, "3.14 % 42 --> real"); + t.is(v21.get_real(), 3.14, EPSILON, "3.14 % 42 --> 3.14"); + + // real / real -> real + Variant v22 = v2 % v2; + t.is(v22.type(), Variant::type_real, "3.14 % 3.14 --> real"); + t.is(v22.get_real(), 0.0, EPSILON, "3.14 % 3.14 --> 0.0"); + + // real / string -> error + try { + Variant v23 = v2 % v3; + t.fail("3.14 % foo --> error"); + } catch (...) { + t.pass("3.14 % foo --> error"); + } + + // real / date -> error + try { + Variant v24 = v2 % v4; + t.fail("3.14 % 1234567890 --> error"); + } catch (...) { + t.pass("3.14 % 1234567890 --> error"); + } + + // real / duration -> duration + try { + Variant v25 = v2 % v5; + t.fail("3.14 % 1200 --> error"); + } catch (...) { + t.pass("3.14 % 1200 --> error"); + } + + // string / boolean -> ERROR + try { + Variant v30 = v3 % v0; + t.fail("foo % true --> error"); + } catch (...) { + t.pass("foo % true --> error"); + } + + // string / integer -> ERROR + try { + Variant v31 = v3 % v1; + t.fail("foo % 42 --> error"); + } catch (...) { + t.pass("foo % 42 --> error"); + } + + // string / real -> ERROR + try { + Variant v32 = v3 % v2; + t.fail("foo % 3.14 --> error"); + } catch (...) { + t.pass("foo % 3.14 --> error"); + } + + // string / string -> ERROR + try { + Variant v33 = v3 % v3; + t.fail("foo % foo --> error"); + } catch (...) { + t.pass("foo % foo --> error"); + } + + // string / date -> ERROR + try { + Variant v34 = v3 % v4; + t.fail("foo % 1234567890 --> error"); + } catch (...) { + t.pass("foo % 1234567890 --> error"); + } + + // string / duration -> ERROR + try { + Variant v35 = v3 % v5; + t.fail("foo % 1200 --> error"); + } catch (...) { + t.pass("foo % 1200 --> error"); + } + + // date / boolean -> ERROR + try { + Variant v40 = v4 % v0; + t.fail("1234567890 % true --> error"); + } catch (...) { + t.pass("1234567890 % true --> error"); + } + + // date / integer -> ERROR + try { + Variant v41 = v4 % v1; + t.fail("1234567890 % 42 --> error"); + } catch (...) { + t.pass("1234567890 % 42 --> error"); + } + + // date / real -> ERROR + try { + Variant v42 = v4 % v2; + t.fail("1234567890 % 3.14 --> error"); + } catch (...) { + t.pass("1234567890 % 3.14 --> error"); + } + + // date / string -> ERROR + try { + Variant v43 = v4 % v3; + t.fail("1234567890 % foo --> error"); + } catch (...) { + t.pass("1234567890 % foo --> error"); + } + + // date / date -> ERROR + try { + Variant v44 = v4 % v4; + t.fail("1234567890 % 1234567890 --> error"); + } catch (...) { + t.pass("1234567890 % 1234567890 --> error"); + } + + // date / duration -> ERROR + try { + Variant v45 = v4 % v5; + t.fail("1234567890 % 1200 --> error"); + } catch (...) { + t.pass("1234567890 % 1200 --> error"); + } + + // duration / boolean -> ERROR + try { + Variant v50 = v5 % v0; + t.fail("1200 % true --> error"); + } catch (...) { + t.pass("1200 % true --> error"); + } + + // duration / integer -> ERROR + try { + Variant v51 = v5 % v1; + t.fail("1200 % 42 --> error"); + } catch (...) { + t.pass("1200 % 42 --> error"); + } + + // duration / real -> ERROR + try { + Variant v52 = v5 % v2; + t.fail("1200 % 3.14 --> error"); + } catch (...) { + t.pass("1200 % 3.14 --> error"); + } + + // duration / string -> ERROR + try { + Variant v53 = v5 % v3; + t.fail("1200 % foo --> error"); + } catch (...) { + t.pass("1200 % foo --> error"); + } + + // duration / date -> ERROR + try { + Variant v54 = v5 % v4; + t.fail("1200 % 1234567890 --> error"); + } catch (...) { + t.pass("1200 % 1234567890 --> error"); + } + + // duration / duration -> ERROR + try { + Variant v55 = v5 % v5; + t.fail("1200 % 1200 --> error"); + } catch (...) { + t.pass("1200 % 1200 --> error"); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_multiply.t.cpp b/test/variant_multiply.t.cpp deleted file mode 100644 index 88f84682a..000000000 --- a/test/variant_multiply.t.cpp +++ /dev/null @@ -1,212 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.0001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (54); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean * boolean -> ERROR - try {Variant v00 = v0 * v0; t.fail ("true * true --> error");} - catch (...) {t.pass ("true * true --> error");} - - // boolean * integer -> integer - Variant v01 = v0 * v1; - t.is (v01.type (), Variant::type_integer, "true * 42 --> integer"); - t.is (v01.get_integer (), 42, "true * 42 --> 42"); - - // boolean * real -> real - Variant v02 = v0 * v2; - t.is (v02.type (), Variant::type_real, "true * 3.14 --> real"); - t.is (v02.get_real (), 3.14, EPSILON, "true * 3.14 --> 3.14"); - - // boolean * string -> string - Variant v03 = v0 * v3; - t.is (v03.type (), Variant::type_string, "true * foo --> real"); - t.is (v03.get_string (), "foo", "true * foo --> foo"); - - // boolean * date -> ERROR - try {Variant v04 = v0 * v4; t.fail ("true * 1234567890 --> error");} - catch (...) {t.pass ("true * 1234567890 --> error");} - - // boolean * duration -> duration - Variant v05 = v0 * v5; - t.is (v05.type (), Variant::type_duration, "true * 1200 --> duration"); - t.is (v05.get_duration (), 1200, "true * 1200 --> 1200"); - - // integer * boolean -> integer - Variant v10 = v1 * v0; - t.is (v10.type (), Variant::type_integer, "42 * true --> integer"); - t.is (v10.get_integer (), 42, "42 * true --> 42"); - - // integer * integer -> integer - Variant v11 = v1 * v1; - t.is (v11.type (), Variant::type_integer, "42 * 42 --> integer"); - t.is (v11.get_integer (), 1764, "42 * 42 --> 1764"); - - // integer * real -> real - Variant v12 = v1 * v2; - t.is (v12.type (), Variant::type_real, "42 * 3.14 --> real"); - t.is (v12.get_real (), 131.88, EPSILON, "42 * 3.14 --> 131.88"); - - // integer * string -> string - Variant v13 = v1 * v3; - t.is (v13.type (), Variant::type_string, "42 * foo --> string"); - t.is (v13.get_string ().substr (0, 10), "foofoofoof", - "42 * foo --> foofoofoofoo..."); - // integer * date -> error - try {Variant v14 = v1 * v4; t.fail ("42 * 1234567890 --> error");} - catch (...) {t.pass ("42 * 1234567890 --> error");} - - // integer * duration -> duration - Variant v15 = v1 * v5; - t.is (v15.type (), Variant::type_duration, "42 * 1200 --> duration"); - t.is (v15.get_duration (), 50400, "42 * 1200 --> 50400"); - - // real * boolean -> real - Variant v20 = v2 * v0; - t.is (v20.type (), Variant::type_real, "3.14 * true --> real"); - t.is (v20.get_real (), 3.14, EPSILON, "3.14 * true --> 3.14"); - - // real * integer -> real - Variant v21 = v2 * v1; - t.is (v21.type (), Variant::type_real, "3.14 * 42 --> real"); - t.is (v21.get_real (), 131.88, EPSILON, "3.14 * 42 --> 131.88"); - - // real * real -> real - Variant v22 = v2 * v2; - t.is (v22.type (), Variant::type_real, "3.14 * 3.14 --> real"); - t.is (v22.get_real (), 9.8596, EPSILON, "3.14 * 3.14 --> 9.8596"); - - // real * string -> error - try {Variant v23 = v2 * v3; t.fail ("3.14 * foo --> error");} - catch (...) {t.pass ("3.14 * foo --> error");} - - // real * date -> error - try {Variant v24 = v2 * v4; t.fail ("3.14 * 1234567890 --> error");} - catch (...) {t.pass ("3.14 * 1234567890 --> error");} - - // real * duration -> duration - Variant v25 = v2 * v5; - t.is (v25.type (), Variant::type_duration, "3.14 * 1200 --> duration"); - t.is (v25.get_duration (), 3768, "3.14 * 1200 --> 3768"); - - // string * boolean -> string - Variant v30 = v3 * v0; - t.is (v30.type (), Variant::type_string, "foo * true --> real"); - t.is (v30.get_string (), "foo", "foo * true --> foo"); - - // string * integer -> string - Variant v31 = v3 * v1; - t.is (v31.type (), Variant::type_string, "foo * 42 --> string"); - t.is (v31.get_string ().substr (0, 10), "foofoofoof", - "foo * 42 --> foofoofoof..."); - - // string * real -> string - try {Variant v32 = v3 * v2; t.fail ("foo * 3.14 --> error");} - catch (...) {t.pass ("foo * 3.14 --> error");} - - // string * string -> string - try {Variant v33 = v3 * v3; t.fail ("foo * foo --> error");} - catch (...) {t.pass ("foo * foo --> error");} - - // string * date -> string - try {Variant v34 = v3 * v4; t.fail ("foo * 1234567890 --> error");} - catch (...) {t.pass ("foo * 1234567890 --> error");} - - // string * duration -> string - try {Variant v35 = v3 * v5; t.fail ("foo * 1200 --> error");} - catch (...) {t.pass ("foo * 1200 --> error");} - - // date * boolean -> ERROR - try {Variant v40 = v4 * v0; t.fail ("1234567890 * true --> error");} - catch (...) {t.pass ("1234567890 * true --> error");} - - // date * integer -> ERROR - try {Variant v41 = v4 * v1; t.fail ("1234567890 * 42 --> error");} - catch (...) {t.pass ("1234567890 * 42 --> error");} - - // date * real -> ERROR - try {Variant v42 = v4 * v2; t.fail ("1234567890 * 3.14 --> error");} - catch (...) {t.pass ("1234567890 * 3.14 --> error");} - - // date * string -> ERROR - try {Variant v43 = v4 * v3; t.fail ("1234567890 * foo --> error");} - catch (...) {t.pass ("1234567890 * foo --> error");} - - // date * date -> ERROR - try {Variant v44 = v4 * v4; t.fail ("1234567890 * 1234567890 --> error");} - catch (...) {t.pass ("1234567890 * 1234567890 --> error");} - - // date * duration -> ERROR - try {Variant v45 = v4 * v5; t.fail ("1234567890 * 1200 --> error");} - catch (...) {t.pass ("1234567890 * 1200 --> error");} - - // duration * boolean -> duration - Variant v50 = v5 * v0; - t.is (v50.type (), Variant::type_duration, "1200 * true --> duration"); - t.is (v50.get_duration (), 1200, "1200 * true --> 1200"); - - // duration * integer -> duration - Variant v51 = v5 * v1; - t.is (v51.type (), Variant::type_duration, "1200 * 42 --> duration"); - t.is (v51.get_duration (), 50400, "1200 * 42 --> 50400"); - - // duration * real -> duration - Variant v52 = v5 * v2; - t.is (v52.type (), Variant::type_duration, "1200 * 3.14 --> duration"); - t.is (v52.get_duration (), 3768, "1200 * 3.14 --> 3768"); - - // duration * string -> string - try {Variant v53 = v5 * v3; t.fail ("1200 * foo --> error");} - catch (...) {t.pass ("1200 * foo --> error");} - - // duration * date -> date - try {Variant v54 = v5 * v4; t.fail ("1200 * 1234567890 --> error");} - catch (...) {t.pass ("1200 * 1234567890 --> error");} - - // duration * duration -> duration - try {Variant v55 = v5 * v5; t.fail ("1200 * 1200 --> error");} - catch (...) {t.pass ("1200 * 1200 --> error");} - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_multiply_test.cpp b/test/variant_multiply_test.cpp new file mode 100644 index 000000000..159fbb8cb --- /dev/null +++ b/test/variant_multiply_test.cpp @@ -0,0 +1,284 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.0001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(54); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean * boolean -> ERROR + try { + Variant v00 = v0 * v0; + t.fail("true * true --> error"); + } catch (...) { + t.pass("true * true --> error"); + } + + // boolean * integer -> integer + Variant v01 = v0 * v1; + t.is(v01.type(), Variant::type_integer, "true * 42 --> integer"); + t.is(v01.get_integer(), 42, "true * 42 --> 42"); + + // boolean * real -> real + Variant v02 = v0 * v2; + t.is(v02.type(), Variant::type_real, "true * 3.14 --> real"); + t.is(v02.get_real(), 3.14, EPSILON, "true * 3.14 --> 3.14"); + + // boolean * string -> string + Variant v03 = v0 * v3; + t.is(v03.type(), Variant::type_string, "true * foo --> real"); + t.is(v03.get_string(), "foo", "true * foo --> foo"); + + // boolean * date -> ERROR + try { + Variant v04 = v0 * v4; + t.fail("true * 1234567890 --> error"); + } catch (...) { + t.pass("true * 1234567890 --> error"); + } + + // boolean * duration -> duration + Variant v05 = v0 * v5; + t.is(v05.type(), Variant::type_duration, "true * 1200 --> duration"); + t.is(v05.get_duration(), 1200, "true * 1200 --> 1200"); + + // integer * boolean -> integer + Variant v10 = v1 * v0; + t.is(v10.type(), Variant::type_integer, "42 * true --> integer"); + t.is(v10.get_integer(), 42, "42 * true --> 42"); + + // integer * integer -> integer + Variant v11 = v1 * v1; + t.is(v11.type(), Variant::type_integer, "42 * 42 --> integer"); + t.is(v11.get_integer(), 1764, "42 * 42 --> 1764"); + + // integer * real -> real + Variant v12 = v1 * v2; + t.is(v12.type(), Variant::type_real, "42 * 3.14 --> real"); + t.is(v12.get_real(), 131.88, EPSILON, "42 * 3.14 --> 131.88"); + + // integer * string -> string + Variant v13 = v1 * v3; + t.is(v13.type(), Variant::type_string, "42 * foo --> string"); + t.is(v13.get_string().substr(0, 10), "foofoofoof", "42 * foo --> foofoofoofoo..."); + // integer * date -> error + try { + Variant v14 = v1 * v4; + t.fail("42 * 1234567890 --> error"); + } catch (...) { + t.pass("42 * 1234567890 --> error"); + } + + // integer * duration -> duration + Variant v15 = v1 * v5; + t.is(v15.type(), Variant::type_duration, "42 * 1200 --> duration"); + t.is(v15.get_duration(), 50400, "42 * 1200 --> 50400"); + + // real * boolean -> real + Variant v20 = v2 * v0; + t.is(v20.type(), Variant::type_real, "3.14 * true --> real"); + t.is(v20.get_real(), 3.14, EPSILON, "3.14 * true --> 3.14"); + + // real * integer -> real + Variant v21 = v2 * v1; + t.is(v21.type(), Variant::type_real, "3.14 * 42 --> real"); + t.is(v21.get_real(), 131.88, EPSILON, "3.14 * 42 --> 131.88"); + + // real * real -> real + Variant v22 = v2 * v2; + t.is(v22.type(), Variant::type_real, "3.14 * 3.14 --> real"); + t.is(v22.get_real(), 9.8596, EPSILON, "3.14 * 3.14 --> 9.8596"); + + // real * string -> error + try { + Variant v23 = v2 * v3; + t.fail("3.14 * foo --> error"); + } catch (...) { + t.pass("3.14 * foo --> error"); + } + + // real * date -> error + try { + Variant v24 = v2 * v4; + t.fail("3.14 * 1234567890 --> error"); + } catch (...) { + t.pass("3.14 * 1234567890 --> error"); + } + + // real * duration -> duration + Variant v25 = v2 * v5; + t.is(v25.type(), Variant::type_duration, "3.14 * 1200 --> duration"); + t.is(v25.get_duration(), 3768, "3.14 * 1200 --> 3768"); + + // string * boolean -> string + Variant v30 = v3 * v0; + t.is(v30.type(), Variant::type_string, "foo * true --> real"); + t.is(v30.get_string(), "foo", "foo * true --> foo"); + + // string * integer -> string + Variant v31 = v3 * v1; + t.is(v31.type(), Variant::type_string, "foo * 42 --> string"); + t.is(v31.get_string().substr(0, 10), "foofoofoof", "foo * 42 --> foofoofoof..."); + + // string * real -> string + try { + Variant v32 = v3 * v2; + t.fail("foo * 3.14 --> error"); + } catch (...) { + t.pass("foo * 3.14 --> error"); + } + + // string * string -> string + try { + Variant v33 = v3 * v3; + t.fail("foo * foo --> error"); + } catch (...) { + t.pass("foo * foo --> error"); + } + + // string * date -> string + try { + Variant v34 = v3 * v4; + t.fail("foo * 1234567890 --> error"); + } catch (...) { + t.pass("foo * 1234567890 --> error"); + } + + // string * duration -> string + try { + Variant v35 = v3 * v5; + t.fail("foo * 1200 --> error"); + } catch (...) { + t.pass("foo * 1200 --> error"); + } + + // date * boolean -> ERROR + try { + Variant v40 = v4 * v0; + t.fail("1234567890 * true --> error"); + } catch (...) { + t.pass("1234567890 * true --> error"); + } + + // date * integer -> ERROR + try { + Variant v41 = v4 * v1; + t.fail("1234567890 * 42 --> error"); + } catch (...) { + t.pass("1234567890 * 42 --> error"); + } + + // date * real -> ERROR + try { + Variant v42 = v4 * v2; + t.fail("1234567890 * 3.14 --> error"); + } catch (...) { + t.pass("1234567890 * 3.14 --> error"); + } + + // date * string -> ERROR + try { + Variant v43 = v4 * v3; + t.fail("1234567890 * foo --> error"); + } catch (...) { + t.pass("1234567890 * foo --> error"); + } + + // date * date -> ERROR + try { + Variant v44 = v4 * v4; + t.fail("1234567890 * 1234567890 --> error"); + } catch (...) { + t.pass("1234567890 * 1234567890 --> error"); + } + + // date * duration -> ERROR + try { + Variant v45 = v4 * v5; + t.fail("1234567890 * 1200 --> error"); + } catch (...) { + t.pass("1234567890 * 1200 --> error"); + } + + // duration * boolean -> duration + Variant v50 = v5 * v0; + t.is(v50.type(), Variant::type_duration, "1200 * true --> duration"); + t.is(v50.get_duration(), 1200, "1200 * true --> 1200"); + + // duration * integer -> duration + Variant v51 = v5 * v1; + t.is(v51.type(), Variant::type_duration, "1200 * 42 --> duration"); + t.is(v51.get_duration(), 50400, "1200 * 42 --> 50400"); + + // duration * real -> duration + Variant v52 = v5 * v2; + t.is(v52.type(), Variant::type_duration, "1200 * 3.14 --> duration"); + t.is(v52.get_duration(), 3768, "1200 * 3.14 --> 3768"); + + // duration * string -> string + try { + Variant v53 = v5 * v3; + t.fail("1200 * foo --> error"); + } catch (...) { + t.pass("1200 * foo --> error"); + } + + // duration * date -> date + try { + Variant v54 = v5 * v4; + t.fail("1200 * 1234567890 --> error"); + } catch (...) { + t.pass("1200 * 1234567890 --> error"); + } + + // duration * duration -> duration + try { + Variant v55 = v5 * v5; + t.fail("1200 * 1200 --> error"); + } catch (...) { + t.pass("1200 * 1200 --> error"); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_nomatch.t.cpp b/test/variant_nomatch.t.cpp deleted file mode 100644 index a811c558e..000000000 --- a/test/variant_nomatch.t.cpp +++ /dev/null @@ -1,297 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -Task task; - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (120); - - Variant vs0 ("untrue"); // !~ true - Variant vs1 (8421); // !~ 42 - Variant vs2 (3.14159); // !~ 3.14 - Variant vs3 ("foolish"); // !~ foo - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // Interesting cases. - Variant vs00 = vs0.operator_nomatch (v0, task); - t.is (vs00.type (), Variant::type_boolean, "untrue !~ true --> boolean"); - t.is (vs00.get_bool (), false, "untrue !~ true --> false"); - - Variant vs01 = vs0.operator_nomatch (v1, task); - t.is (vs01.type (), Variant::type_boolean, "untrue !~ 42 --> boolean"); - t.is (vs01.get_bool (), true, "untrue !~ 42 --> true"); - - Variant vs02 = vs0.operator_nomatch (v2, task); - t.is (vs02.type (), Variant::type_boolean, "untrue !~ 3.14 --> boolean"); - t.is (vs02.get_bool (), true, "untrue !~ 3.14 --> true"); - - Variant vs03 = vs0.operator_nomatch (v3, task); - t.is (vs03.type (), Variant::type_boolean, "untrue !~ 'foo' --> boolean"); - t.is (vs03.get_bool (), true, "untrue !~ 'foo' --> true"); - - Variant vs04 = vs0.operator_nomatch (v4, task); - t.is (vs04.type (), Variant::type_boolean, "untrue !~ 1234567890 --> boolean"); - t.is (vs04.get_bool (), true, "untrue !~ 1234567890 --> true"); - - Variant vs05 = vs0.operator_nomatch (v5, task); - t.is (vs05.type (), Variant::type_boolean, "untrue !~ 1200 --> boolean"); - t.is (vs05.get_bool (), true, "untrue !~ 1200 --> true"); - - Variant vs10 = vs1.operator_nomatch (v0, task); - t.is (vs10.type (), Variant::type_boolean, "8421 !~ true --> boolean"); - t.is (vs10.get_bool (), true, "8421 !~ true --> true"); - - Variant vs11 = vs1.operator_nomatch (v1, task); - t.is (vs11.type (), Variant::type_boolean, "8421 !~ 42 --> boolean"); - t.is (vs11.get_bool (), false, "8421 !~ 42 --> false"); - - Variant vs12 = vs1.operator_nomatch (v2, task); - t.is (vs12.type (), Variant::type_boolean, "8421 !~ 3.14 --> boolean"); - t.is (vs12.get_bool (), true, "8421 !~ 3.14 --> true"); - - Variant vs13 = vs1.operator_nomatch (v3, task); - t.is (vs13.type (), Variant::type_boolean, "8421 !~ 'foo' --> boolean"); - t.is (vs13.get_bool (), true, "8421 !~ 'foo' --> true"); - - Variant vs14 = vs1.operator_nomatch (v4, task); - t.is (vs14.type (), Variant::type_boolean, "8421 !~ 1234567890 --> boolean"); - t.is (vs14.get_bool (), true, "8421 !~ 1234567890 --> true"); - - Variant vs15 = vs1.operator_nomatch (v5, task); - t.is (vs15.type (), Variant::type_boolean, "8421 !~ 1200 --> boolean"); - t.is (vs15.get_bool (), true, "8421 !~ 1200 --> true"); - - Variant vs20 = vs2.operator_nomatch (v0, task); - t.is (vs20.type (), Variant::type_boolean, "3.14159 !~ true --> boolean"); - t.is (vs20.get_bool (), true, "3.14159 !~ true --> true"); - - Variant vs21 = vs2.operator_nomatch (v1, task); - t.is (vs21.type (), Variant::type_boolean, "3.14159 !~ 42 --> boolean"); - t.is (vs21.get_bool (), true, "3.14159 !~ 42 --> true"); - - Variant vs22 = vs2.operator_nomatch (v2, task); - t.is (vs22.type (), Variant::type_boolean, "3.14159 !~ 3.14 --> boolean"); - t.is (vs22.get_bool (), false, "3.14159 !~ 3.14 --> false"); - - Variant vs23 = vs2.operator_nomatch (v3, task); - t.is (vs23.type (), Variant::type_boolean, "3.14159 !~ 'foo' --> boolean"); - t.is (vs23.get_bool (), true, "3.14159 !~ 'foo' --> true"); - - Variant vs24 = vs2.operator_nomatch (v4, task); - t.is (vs24.type (), Variant::type_boolean, "3.14159 !~ 1234567890 --> boolean"); - t.is (vs24.get_bool (), true, "3.14159 !~ 1234567890 --> true"); - - Variant vs25 = vs2.operator_nomatch (v5, task); - t.is (vs25.type (), Variant::type_boolean, "3.14159 !~ 1200 --> boolean"); - t.is (vs25.get_bool (), true, "3.14159 !~ 1200 --> true"); - - Variant vs30 = vs3.operator_nomatch (v0, task); - t.is (vs30.type (), Variant::type_boolean, "foolish !~ true --> boolean"); - t.is (vs30.get_bool (), true, "foolish !~ true --> true"); - - Variant vs31 = vs3.operator_nomatch (v1, task); - t.is (vs31.type (), Variant::type_boolean, "foolish !~ 42 --> boolean"); - t.is (vs31.get_bool (), true, "foolish !~ 42 --> true"); - - Variant vs32 = vs3.operator_nomatch (v2, task); - t.is (vs32.type (), Variant::type_boolean, "foolish !~ 3.14 --> boolean"); - t.is (vs32.get_bool (), true, "foolish !~ 3.14 --> true"); - - Variant vs33 = vs3.operator_nomatch (v3, task); - t.is (vs33.type (), Variant::type_boolean, "foolish !~ 'foo' --> boolean"); - t.is (vs33.get_bool (), false, "foolish !~ 'foo' --> false"); - - Variant vs34 = vs3.operator_nomatch (v4, task); - t.is (vs34.type (), Variant::type_boolean, "foolish !~ 1234567890 --> boolean"); - t.is (vs34.get_bool (), true, "foolish !~ 1234567890 --> true"); - - Variant vs35 = vs3.operator_nomatch (v5, task); - t.is (vs35.type (), Variant::type_boolean, "foolish !~ 1200 --> boolean"); - t.is (vs35.get_bool (), true, "foolish !~ 1200 --> true"); - - // Exhaustive comparisons. - Variant v00 = v0.operator_nomatch (v0, task); - t.is (v00.type (), Variant::type_boolean, "true !~ true --> boolean"); - t.is (v00.get_bool (), false, "true !~ true --> false"); - - Variant v01 = v0.operator_nomatch (v1, task); - t.is (v01.type (), Variant::type_boolean, "true !~ 42 --> boolean"); - t.is (v01.get_bool (), true, "true !~ 42 --> true"); - - Variant v02 = v0.operator_nomatch (v2, task); - t.is (v02.type (), Variant::type_boolean, "true !~ 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true !~ 3.14 --> true"); - - Variant v03 = v0.operator_nomatch (v3, task); - t.is (v03.type (), Variant::type_boolean, "true !~ 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true !~ 'foo' --> true"); - - Variant v04 = v0.operator_nomatch (v4, task); - t.is (v04.type (), Variant::type_boolean, "true !~ 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true !~ 1234567890 --> true"); - - Variant v05 = v0.operator_nomatch (v5, task); - t.is (v05.type (), Variant::type_boolean, "true !~ 1200 --> boolean"); - t.is (v05.get_bool (), true, "true !~ 1200 --> true"); - - Variant v10 = v1.operator_nomatch (v0, task); - t.is (v10.type (), Variant::type_boolean, "42 !~ true --> boolean"); - t.is (v10.get_bool (), true, "42 !~ true --> true"); - - Variant v11 = v1.operator_nomatch (v1, task); - t.is (v11.type (), Variant::type_boolean, "42 !~ 42 --> boolean"); - t.is (v11.get_bool (), false, "42 !~ 42 --> false"); - - Variant v12 = v1.operator_nomatch (v2, task); - t.is (v12.type (), Variant::type_boolean, "42 !~ 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 !~ 3.14 --> true"); - - Variant v13 = v1.operator_nomatch (v3, task); - t.is (v13.type (), Variant::type_boolean, "42 !~ 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 !~ 'foo' --> true"); - - Variant v14 = v1.operator_nomatch (v4, task); - t.is (v04.type (), Variant::type_boolean, "42 !~ 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 !~ 1234567890 --> true"); - - Variant v15 = v1.operator_nomatch (v5, task); - t.is (v15.type (), Variant::type_boolean, "42 !~ 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 !~ 1200 --> true"); - - Variant v20 = v2.operator_nomatch (v0, task); - t.is (v20.type (), Variant::type_boolean, "3.14 !~ true --> boolean"); - t.is (v20.get_bool (), true, "3.14 !~ true --> true"); - - Variant v21 = v2.operator_nomatch (v1, task); - t.is (v21.type (), Variant::type_boolean, "3.14 !~ 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 !~ 42 --> true"); - - Variant v22 = v2.operator_nomatch (v2, task); - t.is (v22.type (), Variant::type_boolean, "3.14 !~ 3.14 --> boolean"); - t.is (v22.get_bool (), false, "3.14 !~ 3.14 --> false"); - - Variant v23 = v2.operator_nomatch (v3, task); - t.is (v23.type (), Variant::type_boolean, "3.14 !~ 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 !~ 'foo' --> true"); - - Variant v24 = v2.operator_nomatch (v4, task); - t.is (v24.type (), Variant::type_boolean, "3.14 !~ 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 !~ 1234567890 --> true"); - - Variant v25 = v2.operator_nomatch (v5, task); - t.is (v25.type (), Variant::type_boolean, "3.14 !~ 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 !~ 1200 --> true"); - - Variant v30 = v3.operator_nomatch (v0, task); - t.is (v30.type (), Variant::type_boolean, "'foo' !~ true --> boolean"); - t.is (v30.get_bool (), true, "'foo' !~ true --> true"); - - Variant v31 = v3.operator_nomatch (v1, task); - t.is (v31.type (), Variant::type_boolean, "'foo' !~ 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' !~ 42 --> true"); - - Variant v32 = v3.operator_nomatch (v2, task); - t.is (v32.type (), Variant::type_boolean, "'foo' !~ 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' !~ 3.14 --> true"); - - Variant v33 = v3.operator_nomatch (v3, task); - t.is (v33.type (), Variant::type_boolean, "'foo' !~ 'foo' --> boolean"); - t.is (v33.get_bool (), false, "'foo' !~ 'foo' --> false"); - - Variant v34 = v3.operator_nomatch (v4, task); - t.is (v34.type (), Variant::type_boolean, "'foo' !~ 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' !~ 1234567890 --> true"); - - Variant v35 = v3.operator_nomatch (v5, task); - t.is (v35.type (), Variant::type_boolean, "'foo' !~ 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' !~ 1200 --> true"); - - Variant v40 = v4.operator_nomatch (v0, task); - t.is (v40.type (), Variant::type_boolean, "1234567890 !~ true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 !~ true --> true"); - - Variant v41 = v4.operator_nomatch (v1, task); - t.is (v41.type (), Variant::type_boolean, "1234567890 !~ 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 !~ 42 --> true"); - - Variant v42 = v4.operator_nomatch (v2, task); - t.is (v42.type (), Variant::type_boolean, "1234567890 !~ 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 !~ 3.14 --> true"); - - Variant v43 = v4.operator_nomatch (v3, task); - t.is (v43.type (), Variant::type_boolean, "1234567890 !~ 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 !~ 'foo' --> true"); - - Variant v44 = v4.operator_nomatch (v4, task); - t.is (v44.type (), Variant::type_boolean, "1234567890 !~ 1234567890 --> boolean"); - t.is (v44.get_bool (), false, "1234567890 !~ 1234567890 --> false"); - - Variant v45 = v4.operator_nomatch (v5, task); - t.is (v45.type (), Variant::type_boolean, "1234567890 !~ 1200 --> boolean"); - t.is (v45.get_bool (), true, "1234567890 !~ 1200 --> true"); - - Variant v50 = v5.operator_nomatch (v0, task); - t.is (v50.type (), Variant::type_boolean, "1200 !~ true --> boolean"); - t.is (v50.get_bool (), true, "1200 !~ true --> true"); - - Variant v51 = v5.operator_nomatch (v1, task); - t.is (v51.type (), Variant::type_boolean, "1200 !~ 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 !~ 42 --> true"); - - Variant v52 = v5.operator_nomatch (v2, task); - t.is (v52.type (), Variant::type_boolean, "1200 !~ 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 !~ 3.14 --> true"); - - Variant v53 = v5.operator_nomatch (v3, task); - t.is (v53.type (), Variant::type_boolean, "1200 !~ 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 !~ 'foo' --> true"); - - Variant v54 = v5.operator_nomatch (v4, task); - t.is (v04.type (), Variant::type_boolean, "1200 !~ 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "1200 !~ 1234567890 --> true"); - - Variant v55 = v5.operator_nomatch (v5, task); - t.is (v55.type (), Variant::type_boolean, "1200 !~ 1200 --> boolean"); - t.is (v55.get_bool (), false, "1200 !~ 1200 --> false"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_nomatch_test.cpp b/test/variant_nomatch_test.cpp new file mode 100644 index 000000000..31820f8ac --- /dev/null +++ b/test/variant_nomatch_test.cpp @@ -0,0 +1,299 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(120); + + Task task; + + Variant vs0("untrue"); // !~ true + Variant vs1(8421); // !~ 42 + Variant vs2(3.14159); // !~ 3.14 + Variant vs3("foolish"); // !~ foo + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // Interesting cases. + Variant vs00 = vs0.operator_nomatch(v0, task); + t.is(vs00.type(), Variant::type_boolean, "untrue !~ true --> boolean"); + t.is(vs00.get_bool(), false, "untrue !~ true --> false"); + + Variant vs01 = vs0.operator_nomatch(v1, task); + t.is(vs01.type(), Variant::type_boolean, "untrue !~ 42 --> boolean"); + t.is(vs01.get_bool(), true, "untrue !~ 42 --> true"); + + Variant vs02 = vs0.operator_nomatch(v2, task); + t.is(vs02.type(), Variant::type_boolean, "untrue !~ 3.14 --> boolean"); + t.is(vs02.get_bool(), true, "untrue !~ 3.14 --> true"); + + Variant vs03 = vs0.operator_nomatch(v3, task); + t.is(vs03.type(), Variant::type_boolean, "untrue !~ 'foo' --> boolean"); + t.is(vs03.get_bool(), true, "untrue !~ 'foo' --> true"); + + Variant vs04 = vs0.operator_nomatch(v4, task); + t.is(vs04.type(), Variant::type_boolean, "untrue !~ 1234567890 --> boolean"); + t.is(vs04.get_bool(), true, "untrue !~ 1234567890 --> true"); + + Variant vs05 = vs0.operator_nomatch(v5, task); + t.is(vs05.type(), Variant::type_boolean, "untrue !~ 1200 --> boolean"); + t.is(vs05.get_bool(), true, "untrue !~ 1200 --> true"); + + Variant vs10 = vs1.operator_nomatch(v0, task); + t.is(vs10.type(), Variant::type_boolean, "8421 !~ true --> boolean"); + t.is(vs10.get_bool(), true, "8421 !~ true --> true"); + + Variant vs11 = vs1.operator_nomatch(v1, task); + t.is(vs11.type(), Variant::type_boolean, "8421 !~ 42 --> boolean"); + t.is(vs11.get_bool(), false, "8421 !~ 42 --> false"); + + Variant vs12 = vs1.operator_nomatch(v2, task); + t.is(vs12.type(), Variant::type_boolean, "8421 !~ 3.14 --> boolean"); + t.is(vs12.get_bool(), true, "8421 !~ 3.14 --> true"); + + Variant vs13 = vs1.operator_nomatch(v3, task); + t.is(vs13.type(), Variant::type_boolean, "8421 !~ 'foo' --> boolean"); + t.is(vs13.get_bool(), true, "8421 !~ 'foo' --> true"); + + Variant vs14 = vs1.operator_nomatch(v4, task); + t.is(vs14.type(), Variant::type_boolean, "8421 !~ 1234567890 --> boolean"); + t.is(vs14.get_bool(), true, "8421 !~ 1234567890 --> true"); + + Variant vs15 = vs1.operator_nomatch(v5, task); + t.is(vs15.type(), Variant::type_boolean, "8421 !~ 1200 --> boolean"); + t.is(vs15.get_bool(), true, "8421 !~ 1200 --> true"); + + Variant vs20 = vs2.operator_nomatch(v0, task); + t.is(vs20.type(), Variant::type_boolean, "3.14159 !~ true --> boolean"); + t.is(vs20.get_bool(), true, "3.14159 !~ true --> true"); + + Variant vs21 = vs2.operator_nomatch(v1, task); + t.is(vs21.type(), Variant::type_boolean, "3.14159 !~ 42 --> boolean"); + t.is(vs21.get_bool(), true, "3.14159 !~ 42 --> true"); + + Variant vs22 = vs2.operator_nomatch(v2, task); + t.is(vs22.type(), Variant::type_boolean, "3.14159 !~ 3.14 --> boolean"); + t.is(vs22.get_bool(), false, "3.14159 !~ 3.14 --> false"); + + Variant vs23 = vs2.operator_nomatch(v3, task); + t.is(vs23.type(), Variant::type_boolean, "3.14159 !~ 'foo' --> boolean"); + t.is(vs23.get_bool(), true, "3.14159 !~ 'foo' --> true"); + + Variant vs24 = vs2.operator_nomatch(v4, task); + t.is(vs24.type(), Variant::type_boolean, "3.14159 !~ 1234567890 --> boolean"); + t.is(vs24.get_bool(), true, "3.14159 !~ 1234567890 --> true"); + + Variant vs25 = vs2.operator_nomatch(v5, task); + t.is(vs25.type(), Variant::type_boolean, "3.14159 !~ 1200 --> boolean"); + t.is(vs25.get_bool(), true, "3.14159 !~ 1200 --> true"); + + Variant vs30 = vs3.operator_nomatch(v0, task); + t.is(vs30.type(), Variant::type_boolean, "foolish !~ true --> boolean"); + t.is(vs30.get_bool(), true, "foolish !~ true --> true"); + + Variant vs31 = vs3.operator_nomatch(v1, task); + t.is(vs31.type(), Variant::type_boolean, "foolish !~ 42 --> boolean"); + t.is(vs31.get_bool(), true, "foolish !~ 42 --> true"); + + Variant vs32 = vs3.operator_nomatch(v2, task); + t.is(vs32.type(), Variant::type_boolean, "foolish !~ 3.14 --> boolean"); + t.is(vs32.get_bool(), true, "foolish !~ 3.14 --> true"); + + Variant vs33 = vs3.operator_nomatch(v3, task); + t.is(vs33.type(), Variant::type_boolean, "foolish !~ 'foo' --> boolean"); + t.is(vs33.get_bool(), false, "foolish !~ 'foo' --> false"); + + Variant vs34 = vs3.operator_nomatch(v4, task); + t.is(vs34.type(), Variant::type_boolean, "foolish !~ 1234567890 --> boolean"); + t.is(vs34.get_bool(), true, "foolish !~ 1234567890 --> true"); + + Variant vs35 = vs3.operator_nomatch(v5, task); + t.is(vs35.type(), Variant::type_boolean, "foolish !~ 1200 --> boolean"); + t.is(vs35.get_bool(), true, "foolish !~ 1200 --> true"); + + // Exhaustive comparisons. + Variant v00 = v0.operator_nomatch(v0, task); + t.is(v00.type(), Variant::type_boolean, "true !~ true --> boolean"); + t.is(v00.get_bool(), false, "true !~ true --> false"); + + Variant v01 = v0.operator_nomatch(v1, task); + t.is(v01.type(), Variant::type_boolean, "true !~ 42 --> boolean"); + t.is(v01.get_bool(), true, "true !~ 42 --> true"); + + Variant v02 = v0.operator_nomatch(v2, task); + t.is(v02.type(), Variant::type_boolean, "true !~ 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true !~ 3.14 --> true"); + + Variant v03 = v0.operator_nomatch(v3, task); + t.is(v03.type(), Variant::type_boolean, "true !~ 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true !~ 'foo' --> true"); + + Variant v04 = v0.operator_nomatch(v4, task); + t.is(v04.type(), Variant::type_boolean, "true !~ 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true !~ 1234567890 --> true"); + + Variant v05 = v0.operator_nomatch(v5, task); + t.is(v05.type(), Variant::type_boolean, "true !~ 1200 --> boolean"); + t.is(v05.get_bool(), true, "true !~ 1200 --> true"); + + Variant v10 = v1.operator_nomatch(v0, task); + t.is(v10.type(), Variant::type_boolean, "42 !~ true --> boolean"); + t.is(v10.get_bool(), true, "42 !~ true --> true"); + + Variant v11 = v1.operator_nomatch(v1, task); + t.is(v11.type(), Variant::type_boolean, "42 !~ 42 --> boolean"); + t.is(v11.get_bool(), false, "42 !~ 42 --> false"); + + Variant v12 = v1.operator_nomatch(v2, task); + t.is(v12.type(), Variant::type_boolean, "42 !~ 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 !~ 3.14 --> true"); + + Variant v13 = v1.operator_nomatch(v3, task); + t.is(v13.type(), Variant::type_boolean, "42 !~ 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 !~ 'foo' --> true"); + + Variant v14 = v1.operator_nomatch(v4, task); + t.is(v04.type(), Variant::type_boolean, "42 !~ 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 !~ 1234567890 --> true"); + + Variant v15 = v1.operator_nomatch(v5, task); + t.is(v15.type(), Variant::type_boolean, "42 !~ 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 !~ 1200 --> true"); + + Variant v20 = v2.operator_nomatch(v0, task); + t.is(v20.type(), Variant::type_boolean, "3.14 !~ true --> boolean"); + t.is(v20.get_bool(), true, "3.14 !~ true --> true"); + + Variant v21 = v2.operator_nomatch(v1, task); + t.is(v21.type(), Variant::type_boolean, "3.14 !~ 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 !~ 42 --> true"); + + Variant v22 = v2.operator_nomatch(v2, task); + t.is(v22.type(), Variant::type_boolean, "3.14 !~ 3.14 --> boolean"); + t.is(v22.get_bool(), false, "3.14 !~ 3.14 --> false"); + + Variant v23 = v2.operator_nomatch(v3, task); + t.is(v23.type(), Variant::type_boolean, "3.14 !~ 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 !~ 'foo' --> true"); + + Variant v24 = v2.operator_nomatch(v4, task); + t.is(v24.type(), Variant::type_boolean, "3.14 !~ 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 !~ 1234567890 --> true"); + + Variant v25 = v2.operator_nomatch(v5, task); + t.is(v25.type(), Variant::type_boolean, "3.14 !~ 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 !~ 1200 --> true"); + + Variant v30 = v3.operator_nomatch(v0, task); + t.is(v30.type(), Variant::type_boolean, "'foo' !~ true --> boolean"); + t.is(v30.get_bool(), true, "'foo' !~ true --> true"); + + Variant v31 = v3.operator_nomatch(v1, task); + t.is(v31.type(), Variant::type_boolean, "'foo' !~ 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' !~ 42 --> true"); + + Variant v32 = v3.operator_nomatch(v2, task); + t.is(v32.type(), Variant::type_boolean, "'foo' !~ 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' !~ 3.14 --> true"); + + Variant v33 = v3.operator_nomatch(v3, task); + t.is(v33.type(), Variant::type_boolean, "'foo' !~ 'foo' --> boolean"); + t.is(v33.get_bool(), false, "'foo' !~ 'foo' --> false"); + + Variant v34 = v3.operator_nomatch(v4, task); + t.is(v34.type(), Variant::type_boolean, "'foo' !~ 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' !~ 1234567890 --> true"); + + Variant v35 = v3.operator_nomatch(v5, task); + t.is(v35.type(), Variant::type_boolean, "'foo' !~ 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' !~ 1200 --> true"); + + Variant v40 = v4.operator_nomatch(v0, task); + t.is(v40.type(), Variant::type_boolean, "1234567890 !~ true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 !~ true --> true"); + + Variant v41 = v4.operator_nomatch(v1, task); + t.is(v41.type(), Variant::type_boolean, "1234567890 !~ 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 !~ 42 --> true"); + + Variant v42 = v4.operator_nomatch(v2, task); + t.is(v42.type(), Variant::type_boolean, "1234567890 !~ 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 !~ 3.14 --> true"); + + Variant v43 = v4.operator_nomatch(v3, task); + t.is(v43.type(), Variant::type_boolean, "1234567890 !~ 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 !~ 'foo' --> true"); + + Variant v44 = v4.operator_nomatch(v4, task); + t.is(v44.type(), Variant::type_boolean, "1234567890 !~ 1234567890 --> boolean"); + t.is(v44.get_bool(), false, "1234567890 !~ 1234567890 --> false"); + + Variant v45 = v4.operator_nomatch(v5, task); + t.is(v45.type(), Variant::type_boolean, "1234567890 !~ 1200 --> boolean"); + t.is(v45.get_bool(), true, "1234567890 !~ 1200 --> true"); + + Variant v50 = v5.operator_nomatch(v0, task); + t.is(v50.type(), Variant::type_boolean, "1200 !~ true --> boolean"); + t.is(v50.get_bool(), true, "1200 !~ true --> true"); + + Variant v51 = v5.operator_nomatch(v1, task); + t.is(v51.type(), Variant::type_boolean, "1200 !~ 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 !~ 42 --> true"); + + Variant v52 = v5.operator_nomatch(v2, task); + t.is(v52.type(), Variant::type_boolean, "1200 !~ 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 !~ 3.14 --> true"); + + Variant v53 = v5.operator_nomatch(v3, task); + t.is(v53.type(), Variant::type_boolean, "1200 !~ 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 !~ 'foo' --> true"); + + Variant v54 = v5.operator_nomatch(v4, task); + t.is(v04.type(), Variant::type_boolean, "1200 !~ 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "1200 !~ 1234567890 --> true"); + + Variant v55 = v5.operator_nomatch(v5, task); + t.is(v55.type(), Variant::type_boolean, "1200 !~ 1200 --> boolean"); + t.is(v55.get_bool(), false, "1200 !~ 1200 --> false"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_not.t.cpp b/test/variant_not_test.cpp similarity index 56% rename from test/variant_not.t.cpp rename to test/variant_not_test.cpp index 4337745d0..15b6ad476 100644 --- a/test/variant_not.t.cpp +++ b/test/variant_not_test.cpp @@ -25,51 +25,53 @@ //////////////////////////////////////////////////////////////////////////////// #include -#include -#include +// cmake.h include header must come first + #include +#include + +#include //////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (14); +int TEST_NAME(int, char**) { + UnitTest t(14); - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); // Truth table. - Variant vFalse (false); - Variant vTrue (true); - t.is (!vFalse, true, "!false --> true"); - t.is (!vTrue, false, "!true --> false"); + Variant vFalse(false); + Variant vTrue(true); + t.is(!vFalse, true, "!false --> true"); + t.is(!vTrue, false, "!true --> false"); - Variant v00 = ! v0; - t.is (v00.type (), Variant::type_boolean, "! true --> boolean"); - t.is (v00.get_bool (), false, "! true --> false"); + Variant v00 = !v0; + t.is(v00.type(), Variant::type_boolean, "! true --> boolean"); + t.is(v00.get_bool(), false, "! true --> false"); - Variant v01 = ! v1; - t.is (v01.type (), Variant::type_boolean, "! 42 --> boolean"); - t.is (v01.get_bool (), false, "! 42 --> false"); + Variant v01 = !v1; + t.is(v01.type(), Variant::type_boolean, "! 42 --> boolean"); + t.is(v01.get_bool(), false, "! 42 --> false"); - Variant v02 = ! v2; - t.is (v02.type (), Variant::type_boolean, "! 3.14 --> boolean"); - t.is (v02.get_bool (), false, "! 3.14 --> false"); + Variant v02 = !v2; + t.is(v02.type(), Variant::type_boolean, "! 3.14 --> boolean"); + t.is(v02.get_bool(), false, "! 3.14 --> false"); - Variant v03 = ! v3; - t.is (v03.type (), Variant::type_boolean, "! foo --> boolean"); - t.is (v03.get_bool (), false, "! foo --> false"); + Variant v03 = !v3; + t.is(v03.type(), Variant::type_boolean, "! foo --> boolean"); + t.is(v03.get_bool(), false, "! foo --> false"); - Variant v04 = ! v4; - t.is (v04.type (), Variant::type_boolean, "! 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "! 1234567890 --> false"); + Variant v04 = !v4; + t.is(v04.type(), Variant::type_boolean, "! 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "! 1234567890 --> false"); - Variant v05 = ! v5; - t.is (v05.type (), Variant::type_boolean, "! 1200 --> boolean"); - t.is (v05.get_bool (), false, "! 1200 --> false"); + Variant v05 = !v5; + t.is(v05.type(), Variant::type_boolean, "! 1200 --> boolean"); + t.is(v05.get_bool(), false, "! 1200 --> false"); return 0; } diff --git a/test/variant_or.t.cpp b/test/variant_or.t.cpp deleted file mode 100644 index 8b097b9e9..000000000 --- a/test/variant_or.t.cpp +++ /dev/null @@ -1,199 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (76); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // Truth table. - Variant vFalse (false); - Variant vTrue (true); - t.is (vFalse || vFalse, false, "false || false --> false"); - t.is (vFalse || vTrue, true, "false || true --> true"); - t.is (vTrue || vFalse, true, "true || false --> true"); - t.is (vTrue || vTrue, true, "true || true --> true"); - - Variant v00 = v0 || v0; - t.is (v00.type (), Variant::type_boolean, "true || true --> boolean"); - t.is (v00.get_bool (), true, "true || true --> true"); - - Variant v01 = v0 || v1; - t.is (v01.type (), Variant::type_boolean, "true || 42 --> boolean"); - t.is (v01.get_bool (), true, "true || 42 --> true"); - - Variant v02 = v0 || v2; - t.is (v02.type (), Variant::type_boolean, "true || 3.14 --> boolean"); - t.is (v02.get_bool (), true, "true || 3.14 --> true"); - - Variant v03 = v0 || v3; - t.is (v03.type (), Variant::type_boolean, "true || 'foo' --> boolean"); - t.is (v03.get_bool (), true, "true || 'foo' --> true"); - - Variant v04 = v0 || v4; - t.is (v04.type (), Variant::type_boolean, "true || 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "true || 1234567890 --> true"); - - Variant v05 = v0 || v5; - t.is (v05.type (), Variant::type_boolean, "true || 1200 --> boolean"); - t.is (v05.get_bool (), true, "true || 1200 --> true"); - - Variant v10 = v1 || v0; - t.is (v10.type (), Variant::type_boolean, "42 || true --> boolean"); - t.is (v10.get_bool (), true, "42 || true --> true"); - - Variant v11 = v1 || v1; - t.is (v11.type (), Variant::type_boolean, "42 || 42 --> boolean"); - t.is (v11.get_bool (), true, "42 || 42 --> true"); - - Variant v12 = v1 || v2; - t.is (v12.type (), Variant::type_boolean, "42 || 3.14 --> boolean"); - t.is (v12.get_bool (), true, "42 || 3.14 --> true"); - - Variant v13 = v1 || v3; - t.is (v13.type (), Variant::type_boolean, "42 || 'foo' --> boolean"); - t.is (v13.get_bool (), true, "42 || 'foo' --> true"); - - Variant v14 = v1 || v4; - t.is (v04.type (), Variant::type_boolean, "42 || 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "42 || 1234567890 --> true"); - - Variant v15 = v1 || v5; - t.is (v15.type (), Variant::type_boolean, "42 || 1200 --> boolean"); - t.is (v15.get_bool (), true, "42 || 1200 --> true"); - - Variant v20 = v2 || v0; - t.is (v20.type (), Variant::type_boolean, "3.14 || true --> boolean"); - t.is (v20.get_bool (), true, "3.14 || true --> true"); - - Variant v21 = v2 || v1; - t.is (v21.type (), Variant::type_boolean, "3.14 || 42 --> boolean"); - t.is (v21.get_bool (), true, "3.14 || 42 --> true"); - - Variant v22 = v2 || v2; - t.is (v22.type (), Variant::type_boolean, "3.14 || 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 || 3.14 --> true"); - - Variant v23 = v2 || v3; - t.is (v23.type (), Variant::type_boolean, "3.14 || 'foo' --> boolean"); - t.is (v23.get_bool (), true, "3.14 || 'foo' --> true"); - - Variant v24 = v2 || v4; - t.is (v24.type (), Variant::type_boolean, "3.14 || 1234567890 --> boolean"); - t.is (v24.get_bool (), true, "3.14 || 1234567890 --> true"); - - Variant v25 = v2 || v5; - t.is (v25.type (), Variant::type_boolean, "3.14 || 1200 --> boolean"); - t.is (v25.get_bool (), true, "3.14 || 1200 --> true"); - - Variant v30 = v3 || v0; - t.is (v30.type (), Variant::type_boolean, "'foo' || true --> boolean"); - t.is (v30.get_bool (), true, "'foo' || true --> true"); - - Variant v31 = v3 || v1; - t.is (v31.type (), Variant::type_boolean, "'foo' || 42 --> boolean"); - t.is (v31.get_bool (), true, "'foo' || 42 --> true"); - - Variant v32 = v3 || v2; - t.is (v32.type (), Variant::type_boolean, "'foo' || 3.14 --> boolean"); - t.is (v32.get_bool (), true, "'foo' || 3.14 --> true"); - - Variant v33 = v3 || v3; - t.is (v33.type (), Variant::type_boolean, "'foo' || 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' || 'foo' --> true"); - - Variant v34 = v3 || v4; - t.is (v34.type (), Variant::type_boolean, "'foo' || 1234567890 --> boolean"); - t.is (v34.get_bool (), true, "'foo' || 1234567890 --> true"); - - Variant v35 = v3 || v5; - t.is (v35.type (), Variant::type_boolean, "'foo' || 1200 --> boolean"); - t.is (v35.get_bool (), true, "'foo' || 1200 --> true"); - - Variant v40 = v4 || v0; - t.is (v40.type (), Variant::type_boolean, "1234567890 || true --> boolean"); - t.is (v40.get_bool (), true, "1234567890 || true --> true"); - - Variant v41 = v4 || v1; - t.is (v41.type (), Variant::type_boolean, "1234567890 || 42 --> boolean"); - t.is (v41.get_bool (), true, "1234567890 || 42 --> true"); - - Variant v42 = v4 || v2; - t.is (v42.type (), Variant::type_boolean, "1234567890 || 3.14 --> boolean"); - t.is (v42.get_bool (), true, "1234567890 || 3.14 --> true"); - - Variant v43 = v4 || v3; - t.is (v43.type (), Variant::type_boolean, "1234567890 || 'foo' --> boolean"); - t.is (v43.get_bool (), true, "1234567890 || 'foo' --> true"); - - Variant v44 = v4 || v4; - t.is (v44.type (), Variant::type_boolean, "1234567890 || 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 || 1234567890 --> true"); - - Variant v45 = v4 || v5; - t.is (v45.type (), Variant::type_boolean, "1234567890 || 1200 --> boolean"); - t.is (v45.get_bool (), true, "1234567890 || 1200 --> true"); - - Variant v50 = v5 || v0; - t.is (v50.type (), Variant::type_boolean, "1200 || true --> boolean"); - t.is (v50.get_bool (), true, "1200 || true --> true"); - - Variant v51 = v5 || v1; - t.is (v51.type (), Variant::type_boolean, "1200 || 42 --> boolean"); - t.is (v51.get_bool (), true, "1200 || 42 --> true"); - - Variant v52 = v5 || v2; - t.is (v52.type (), Variant::type_boolean, "1200 || 3.14 --> boolean"); - t.is (v52.get_bool (), true, "1200 || 3.14 --> true"); - - Variant v53 = v5 || v3; - t.is (v53.type (), Variant::type_boolean, "1200 || 'foo' --> boolean"); - t.is (v53.get_bool (), true, "1200 || 'foo' --> true"); - - Variant v54 = v5 || v4; - t.is (v04.type (), Variant::type_boolean, "1200 || 1234567890 --> boolean"); - t.is (v04.get_bool (), true, "1200 || 1234567890 --> true"); - - Variant v55 = v5 || v5; - t.is (v55.type (), Variant::type_boolean, "1200 || 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 || 1200 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_or_test.cpp b/test/variant_or_test.cpp new file mode 100644 index 000000000..9c91e2151 --- /dev/null +++ b/test/variant_or_test.cpp @@ -0,0 +1,201 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(76); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // Truth table. + Variant vFalse(false); + Variant vTrue(true); + t.is(vFalse || vFalse, false, "false || false --> false"); + t.is(vFalse || vTrue, true, "false || true --> true"); + t.is(vTrue || vFalse, true, "true || false --> true"); + t.is(vTrue || vTrue, true, "true || true --> true"); + + Variant v00 = v0 || v0; + t.is(v00.type(), Variant::type_boolean, "true || true --> boolean"); + t.is(v00.get_bool(), true, "true || true --> true"); + + Variant v01 = v0 || v1; + t.is(v01.type(), Variant::type_boolean, "true || 42 --> boolean"); + t.is(v01.get_bool(), true, "true || 42 --> true"); + + Variant v02 = v0 || v2; + t.is(v02.type(), Variant::type_boolean, "true || 3.14 --> boolean"); + t.is(v02.get_bool(), true, "true || 3.14 --> true"); + + Variant v03 = v0 || v3; + t.is(v03.type(), Variant::type_boolean, "true || 'foo' --> boolean"); + t.is(v03.get_bool(), true, "true || 'foo' --> true"); + + Variant v04 = v0 || v4; + t.is(v04.type(), Variant::type_boolean, "true || 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "true || 1234567890 --> true"); + + Variant v05 = v0 || v5; + t.is(v05.type(), Variant::type_boolean, "true || 1200 --> boolean"); + t.is(v05.get_bool(), true, "true || 1200 --> true"); + + Variant v10 = v1 || v0; + t.is(v10.type(), Variant::type_boolean, "42 || true --> boolean"); + t.is(v10.get_bool(), true, "42 || true --> true"); + + Variant v11 = v1 || v1; + t.is(v11.type(), Variant::type_boolean, "42 || 42 --> boolean"); + t.is(v11.get_bool(), true, "42 || 42 --> true"); + + Variant v12 = v1 || v2; + t.is(v12.type(), Variant::type_boolean, "42 || 3.14 --> boolean"); + t.is(v12.get_bool(), true, "42 || 3.14 --> true"); + + Variant v13 = v1 || v3; + t.is(v13.type(), Variant::type_boolean, "42 || 'foo' --> boolean"); + t.is(v13.get_bool(), true, "42 || 'foo' --> true"); + + Variant v14 = v1 || v4; + t.is(v04.type(), Variant::type_boolean, "42 || 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "42 || 1234567890 --> true"); + + Variant v15 = v1 || v5; + t.is(v15.type(), Variant::type_boolean, "42 || 1200 --> boolean"); + t.is(v15.get_bool(), true, "42 || 1200 --> true"); + + Variant v20 = v2 || v0; + t.is(v20.type(), Variant::type_boolean, "3.14 || true --> boolean"); + t.is(v20.get_bool(), true, "3.14 || true --> true"); + + Variant v21 = v2 || v1; + t.is(v21.type(), Variant::type_boolean, "3.14 || 42 --> boolean"); + t.is(v21.get_bool(), true, "3.14 || 42 --> true"); + + Variant v22 = v2 || v2; + t.is(v22.type(), Variant::type_boolean, "3.14 || 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 || 3.14 --> true"); + + Variant v23 = v2 || v3; + t.is(v23.type(), Variant::type_boolean, "3.14 || 'foo' --> boolean"); + t.is(v23.get_bool(), true, "3.14 || 'foo' --> true"); + + Variant v24 = v2 || v4; + t.is(v24.type(), Variant::type_boolean, "3.14 || 1234567890 --> boolean"); + t.is(v24.get_bool(), true, "3.14 || 1234567890 --> true"); + + Variant v25 = v2 || v5; + t.is(v25.type(), Variant::type_boolean, "3.14 || 1200 --> boolean"); + t.is(v25.get_bool(), true, "3.14 || 1200 --> true"); + + Variant v30 = v3 || v0; + t.is(v30.type(), Variant::type_boolean, "'foo' || true --> boolean"); + t.is(v30.get_bool(), true, "'foo' || true --> true"); + + Variant v31 = v3 || v1; + t.is(v31.type(), Variant::type_boolean, "'foo' || 42 --> boolean"); + t.is(v31.get_bool(), true, "'foo' || 42 --> true"); + + Variant v32 = v3 || v2; + t.is(v32.type(), Variant::type_boolean, "'foo' || 3.14 --> boolean"); + t.is(v32.get_bool(), true, "'foo' || 3.14 --> true"); + + Variant v33 = v3 || v3; + t.is(v33.type(), Variant::type_boolean, "'foo' || 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' || 'foo' --> true"); + + Variant v34 = v3 || v4; + t.is(v34.type(), Variant::type_boolean, "'foo' || 1234567890 --> boolean"); + t.is(v34.get_bool(), true, "'foo' || 1234567890 --> true"); + + Variant v35 = v3 || v5; + t.is(v35.type(), Variant::type_boolean, "'foo' || 1200 --> boolean"); + t.is(v35.get_bool(), true, "'foo' || 1200 --> true"); + + Variant v40 = v4 || v0; + t.is(v40.type(), Variant::type_boolean, "1234567890 || true --> boolean"); + t.is(v40.get_bool(), true, "1234567890 || true --> true"); + + Variant v41 = v4 || v1; + t.is(v41.type(), Variant::type_boolean, "1234567890 || 42 --> boolean"); + t.is(v41.get_bool(), true, "1234567890 || 42 --> true"); + + Variant v42 = v4 || v2; + t.is(v42.type(), Variant::type_boolean, "1234567890 || 3.14 --> boolean"); + t.is(v42.get_bool(), true, "1234567890 || 3.14 --> true"); + + Variant v43 = v4 || v3; + t.is(v43.type(), Variant::type_boolean, "1234567890 || 'foo' --> boolean"); + t.is(v43.get_bool(), true, "1234567890 || 'foo' --> true"); + + Variant v44 = v4 || v4; + t.is(v44.type(), Variant::type_boolean, "1234567890 || 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 || 1234567890 --> true"); + + Variant v45 = v4 || v5; + t.is(v45.type(), Variant::type_boolean, "1234567890 || 1200 --> boolean"); + t.is(v45.get_bool(), true, "1234567890 || 1200 --> true"); + + Variant v50 = v5 || v0; + t.is(v50.type(), Variant::type_boolean, "1200 || true --> boolean"); + t.is(v50.get_bool(), true, "1200 || true --> true"); + + Variant v51 = v5 || v1; + t.is(v51.type(), Variant::type_boolean, "1200 || 42 --> boolean"); + t.is(v51.get_bool(), true, "1200 || 42 --> true"); + + Variant v52 = v5 || v2; + t.is(v52.type(), Variant::type_boolean, "1200 || 3.14 --> boolean"); + t.is(v52.get_bool(), true, "1200 || 3.14 --> true"); + + Variant v53 = v5 || v3; + t.is(v53.type(), Variant::type_boolean, "1200 || 'foo' --> boolean"); + t.is(v53.get_bool(), true, "1200 || 'foo' --> true"); + + Variant v54 = v5 || v4; + t.is(v04.type(), Variant::type_boolean, "1200 || 1234567890 --> boolean"); + t.is(v04.get_bool(), true, "1200 || 1234567890 --> true"); + + Variant v55 = v5 || v5; + t.is(v55.type(), Variant::type_boolean, "1200 || 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 || 1200 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_partial.t.cpp b/test/variant_partial.t.cpp deleted file mode 100644 index a545979c0..000000000 --- a/test/variant_partial.t.cpp +++ /dev/null @@ -1,201 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (72); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - Variant v6 (1234522800, Variant::type_date); // 2009-02-13, 12.00pm UTC - Variant v7 ("2009-02-13"); - - Variant v00 = v0.operator_partial (v0); - t.is (v00.type (), Variant::type_boolean, "true == true --> boolean"); - t.is (v00.get_bool (), true, "true == true --> true"); - - Variant v01 = v0.operator_partial (v1); - t.is (v01.type (), Variant::type_boolean, "true == 42 --> boolean"); - t.is (v01.get_bool (), false, "true == 42 --> false"); - - Variant v02 = v0.operator_partial (v2); - t.is (v02.type (), Variant::type_boolean, "true == 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true == 3.14 --> false"); - - Variant v03 = v0.operator_partial (v3); - t.is (v03.type (), Variant::type_boolean, "true == 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true == 'foo' --> false"); - - Variant v04 = v0.operator_partial (v4); - t.is (v04.type (), Variant::type_boolean, "true == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true == 1234567890 --> false"); - - Variant v05 = v0.operator_partial (v5); - t.is (v05.type (), Variant::type_boolean, "true == 1200 --> boolean"); - t.is (v05.get_bool (), false, "true == 1200 --> false"); - - Variant v10 = v1.operator_partial (v0); - t.is (v10.type (), Variant::type_boolean, "42 == true --> boolean"); - t.is (v10.get_bool (), false, "42 == true --> false"); - - Variant v11 = v1.operator_partial (v1); - t.is (v11.type (), Variant::type_boolean, "42 == 42 --> boolean"); - t.is (v11.get_bool (), true, "42 == 42 --> true"); - - Variant v12 = v1.operator_partial (v2); - t.is (v12.type (), Variant::type_boolean, "42 == 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 == 3.14 --> false"); - - Variant v13 = v1.operator_partial (v3); - t.is (v13.type (), Variant::type_boolean, "42 == 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 == 'foo' --> false"); - - Variant v14 = v1.operator_partial (v4); - t.is (v04.type (), Variant::type_boolean, "42 == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 == 1234567890 --> false"); - - Variant v15 = v1.operator_partial (v5); - t.is (v15.type (), Variant::type_boolean, "42 == 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 == 1200 --> false"); - - Variant v20 = v2.operator_partial (v0); - t.is (v20.type (), Variant::type_boolean, "3.14 == true --> boolean"); - t.is (v20.get_bool (), false, "3.14 == true --> false"); - - Variant v21 = v2.operator_partial (v1); - t.is (v21.type (), Variant::type_boolean, "3.14 == 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 == 42 --> false"); - - Variant v22 = v2.operator_partial (v2); - t.is (v22.type (), Variant::type_boolean, "3.14 == 3.14 --> boolean"); - t.is (v22.get_bool (), true, "3.14 == 3.14 --> true"); - - Variant v23 = v2.operator_partial (v3); - t.is (v23.type (), Variant::type_boolean, "3.14 == 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 == 'foo' --> false"); - - Variant v24 = v2.operator_partial (v4); - t.is (v24.type (), Variant::type_boolean, "3.14 == 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 == 1234567890 --> false"); - - Variant v25 = v2.operator_partial (v5); - t.is (v25.type (), Variant::type_boolean, "3.14 == 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 == 1200 --> false"); - - Variant v30 = v3.operator_partial (v0); - t.is (v30.type (), Variant::type_boolean, "'foo' == true --> boolean"); - t.is (v30.get_bool (), false, "'foo' == true --> false"); - - Variant v31 = v3.operator_partial (v1); - t.is (v31.type (), Variant::type_boolean, "'foo' == 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' == 42 --> false"); - - Variant v32 = v3.operator_partial (v2); - t.is (v32.type (), Variant::type_boolean, "'foo' == 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' == 3.14 --> false"); - - Variant v33 = v3.operator_partial (v3); - t.is (v33.type (), Variant::type_boolean, "'foo' == 'foo' --> boolean"); - t.is (v33.get_bool (), true, "'foo' == 'foo' --> true"); - - Variant v34 = v3.operator_partial (v4); - t.is (v34.type (), Variant::type_boolean, "'foo' == 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' == 1234567890 --> false"); - - Variant v35 = v3.operator_partial (v5); - t.is (v35.type (), Variant::type_boolean, "'foo' == 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' == 1200 --> false"); - - Variant v40 = v4.operator_partial (v0); - t.is (v40.type (), Variant::type_boolean, "1234567890 == true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 == true --> false"); - - Variant v41 = v4.operator_partial (v1); - t.is (v41.type (), Variant::type_boolean, "1234567890 == 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 == 42 --> false"); - - Variant v42 = v4.operator_partial (v2); - t.is (v42.type (), Variant::type_boolean, "1234567890 == 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 == 3.14 --> false"); - - Variant v43 = v4.operator_partial (v3); - t.is (v43.type (), Variant::type_boolean, "1234567890 == 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 == 'foo' --> false"); - - Variant v44 = v4.operator_partial (v4); - t.is (v44.type (), Variant::type_boolean, "1234567890 == 1234567890 --> boolean"); - t.is (v44.get_bool (), true, "1234567890 == 1234567890 --> true"); - - Variant v45 = v4.operator_partial (v5); - t.is (v45.type (), Variant::type_boolean, "1234567890 == 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 == 1200 --> false"); - - Variant v50 = v5.operator_partial (v0); - t.is (v50.type (), Variant::type_boolean, "1200 == true --> boolean"); - t.is (v50.get_bool (), false, "1200 == true --> false"); - - Variant v51 = v5.operator_partial (v1); - t.is (v51.type (), Variant::type_boolean, "1200 == 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 == 42 --> false"); - - Variant v52 = v5.operator_partial (v2); - t.is (v52.type (), Variant::type_boolean, "1200 == 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 == 3.14 --> false"); - - Variant v53 = v5.operator_partial (v3); - t.is (v53.type (), Variant::type_boolean, "1200 == 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 == 'foo' --> false"); - - Variant v54 = v5.operator_partial (v4); - t.is (v04.type (), Variant::type_boolean, "1200 == 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "1200 == 1234567890 --> false"); - - Variant v55 = v5.operator_partial (v5); - t.is (v55.type (), Variant::type_boolean, "1200 == 1200 --> boolean"); - t.is (v55.get_bool (), true, "1200 == 1200 --> true"); - - Variant v56 = v6.operator_partial (v7); - t.is (v56.type (), Variant::type_boolean, "1234522800 == '2009-02-13' --> boolean"); - t.is (v56.get_bool (), true, "1234522800 == '2009-02-13' --> true"); - - Variant v57 = v7.operator_partial (v6); - t.is (v57.type (), Variant::type_boolean, "'2009-02-13' == 1234522800 --> boolean"); - t.is (v57.get_bool (), true, "'2009-02-13' == 1234522800 --> true"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_partial_test.cpp b/test/variant_partial_test.cpp new file mode 100644 index 000000000..40eef0c48 --- /dev/null +++ b/test/variant_partial_test.cpp @@ -0,0 +1,203 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(72); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + Variant v6(1234522800, Variant::type_date); // 2009-02-13, 12.00pm UTC + Variant v7("2009-02-13"); + + Variant v00 = v0.operator_partial(v0); + t.is(v00.type(), Variant::type_boolean, "true == true --> boolean"); + t.is(v00.get_bool(), true, "true == true --> true"); + + Variant v01 = v0.operator_partial(v1); + t.is(v01.type(), Variant::type_boolean, "true == 42 --> boolean"); + t.is(v01.get_bool(), false, "true == 42 --> false"); + + Variant v02 = v0.operator_partial(v2); + t.is(v02.type(), Variant::type_boolean, "true == 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true == 3.14 --> false"); + + Variant v03 = v0.operator_partial(v3); + t.is(v03.type(), Variant::type_boolean, "true == 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true == 'foo' --> false"); + + Variant v04 = v0.operator_partial(v4); + t.is(v04.type(), Variant::type_boolean, "true == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true == 1234567890 --> false"); + + Variant v05 = v0.operator_partial(v5); + t.is(v05.type(), Variant::type_boolean, "true == 1200 --> boolean"); + t.is(v05.get_bool(), false, "true == 1200 --> false"); + + Variant v10 = v1.operator_partial(v0); + t.is(v10.type(), Variant::type_boolean, "42 == true --> boolean"); + t.is(v10.get_bool(), false, "42 == true --> false"); + + Variant v11 = v1.operator_partial(v1); + t.is(v11.type(), Variant::type_boolean, "42 == 42 --> boolean"); + t.is(v11.get_bool(), true, "42 == 42 --> true"); + + Variant v12 = v1.operator_partial(v2); + t.is(v12.type(), Variant::type_boolean, "42 == 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 == 3.14 --> false"); + + Variant v13 = v1.operator_partial(v3); + t.is(v13.type(), Variant::type_boolean, "42 == 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 == 'foo' --> false"); + + Variant v14 = v1.operator_partial(v4); + t.is(v04.type(), Variant::type_boolean, "42 == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 == 1234567890 --> false"); + + Variant v15 = v1.operator_partial(v5); + t.is(v15.type(), Variant::type_boolean, "42 == 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 == 1200 --> false"); + + Variant v20 = v2.operator_partial(v0); + t.is(v20.type(), Variant::type_boolean, "3.14 == true --> boolean"); + t.is(v20.get_bool(), false, "3.14 == true --> false"); + + Variant v21 = v2.operator_partial(v1); + t.is(v21.type(), Variant::type_boolean, "3.14 == 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 == 42 --> false"); + + Variant v22 = v2.operator_partial(v2); + t.is(v22.type(), Variant::type_boolean, "3.14 == 3.14 --> boolean"); + t.is(v22.get_bool(), true, "3.14 == 3.14 --> true"); + + Variant v23 = v2.operator_partial(v3); + t.is(v23.type(), Variant::type_boolean, "3.14 == 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 == 'foo' --> false"); + + Variant v24 = v2.operator_partial(v4); + t.is(v24.type(), Variant::type_boolean, "3.14 == 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 == 1234567890 --> false"); + + Variant v25 = v2.operator_partial(v5); + t.is(v25.type(), Variant::type_boolean, "3.14 == 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 == 1200 --> false"); + + Variant v30 = v3.operator_partial(v0); + t.is(v30.type(), Variant::type_boolean, "'foo' == true --> boolean"); + t.is(v30.get_bool(), false, "'foo' == true --> false"); + + Variant v31 = v3.operator_partial(v1); + t.is(v31.type(), Variant::type_boolean, "'foo' == 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' == 42 --> false"); + + Variant v32 = v3.operator_partial(v2); + t.is(v32.type(), Variant::type_boolean, "'foo' == 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' == 3.14 --> false"); + + Variant v33 = v3.operator_partial(v3); + t.is(v33.type(), Variant::type_boolean, "'foo' == 'foo' --> boolean"); + t.is(v33.get_bool(), true, "'foo' == 'foo' --> true"); + + Variant v34 = v3.operator_partial(v4); + t.is(v34.type(), Variant::type_boolean, "'foo' == 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' == 1234567890 --> false"); + + Variant v35 = v3.operator_partial(v5); + t.is(v35.type(), Variant::type_boolean, "'foo' == 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' == 1200 --> false"); + + Variant v40 = v4.operator_partial(v0); + t.is(v40.type(), Variant::type_boolean, "1234567890 == true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 == true --> false"); + + Variant v41 = v4.operator_partial(v1); + t.is(v41.type(), Variant::type_boolean, "1234567890 == 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 == 42 --> false"); + + Variant v42 = v4.operator_partial(v2); + t.is(v42.type(), Variant::type_boolean, "1234567890 == 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 == 3.14 --> false"); + + Variant v43 = v4.operator_partial(v3); + t.is(v43.type(), Variant::type_boolean, "1234567890 == 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 == 'foo' --> false"); + + Variant v44 = v4.operator_partial(v4); + t.is(v44.type(), Variant::type_boolean, "1234567890 == 1234567890 --> boolean"); + t.is(v44.get_bool(), true, "1234567890 == 1234567890 --> true"); + + Variant v45 = v4.operator_partial(v5); + t.is(v45.type(), Variant::type_boolean, "1234567890 == 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 == 1200 --> false"); + + Variant v50 = v5.operator_partial(v0); + t.is(v50.type(), Variant::type_boolean, "1200 == true --> boolean"); + t.is(v50.get_bool(), false, "1200 == true --> false"); + + Variant v51 = v5.operator_partial(v1); + t.is(v51.type(), Variant::type_boolean, "1200 == 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 == 42 --> false"); + + Variant v52 = v5.operator_partial(v2); + t.is(v52.type(), Variant::type_boolean, "1200 == 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 == 3.14 --> false"); + + Variant v53 = v5.operator_partial(v3); + t.is(v53.type(), Variant::type_boolean, "1200 == 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 == 'foo' --> false"); + + Variant v54 = v5.operator_partial(v4); + t.is(v04.type(), Variant::type_boolean, "1200 == 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "1200 == 1234567890 --> false"); + + Variant v55 = v5.operator_partial(v5); + t.is(v55.type(), Variant::type_boolean, "1200 == 1200 --> boolean"); + t.is(v55.get_bool(), true, "1200 == 1200 --> true"); + + Variant v56 = v6.operator_partial(v7); + t.is(v56.type(), Variant::type_boolean, "1234522800 == '2009-02-13' --> boolean"); + t.is(v56.get_bool(), true, "1234522800 == '2009-02-13' --> true"); + + Variant v57 = v7.operator_partial(v6); + t.is(v57.type(), Variant::type_boolean, "'2009-02-13' == 1234522800 --> boolean"); + t.is(v57.get_bool(), true, "'2009-02-13' == 1234522800 --> true"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_subtract.t.cpp b/test/variant_subtract.t.cpp deleted file mode 100644 index 331073e38..000000000 --- a/test/variant_subtract.t.cpp +++ /dev/null @@ -1,215 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#define EPSILON 0.001 - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (55); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // boolean - boolean -> ERROR - try {Variant v00 = v0 - v0; t.fail ("true - true --> error");} - catch (...) {t.pass ("true - true --> error");} - - // boolean - integer -> ERROR - try {Variant v01 = v0 - v1; t.fail ("true - 42 --> error");} - catch (...) {t.pass ("true - 42 --> error");} - - // boolean - real -> ERROR - try {Variant v02 = v0 - v2; t.fail ("true - 3.14 --> error");} - catch (...) {t.pass ("true - 3.14 --> error");} - - // boolean - string -> ERROR - try {Variant v03 = v0 - v3; t.fail ("true - foo --> error");} - catch (...) {t.pass ("true - foo --> error");} - - // boolean - date -> ERROR - try {Variant v04 = v0 - v4; t.fail ("true - 1234567890 --> error");} - catch (...) {t.pass ("true - 1234567890 --> error");} - - // boolean - duration -> ERROR - try {Variant v05 = v0 - v5; t.fail ("true - 1200 --> error");} - catch (...) {t.pass ("true - 1200 --> error");} - - // integer - boolean -> integer - Variant v10 = v1 - v0; - t.is (v10.type (), Variant::type_integer, "42 - true --> integer"); - t.is (v10.get_integer (), 41, "42 - true --> 41"); - - // integer - integer -> integer - Variant v11 = v1 - v1; - t.is (v11.type (), Variant::type_integer, "42 - 42 --> integer"); - t.is (v11.get_integer (), 0, "42 - 42 --> 0"); - - // integer - real -> real - Variant v12 = v1 - v2; - t.is (v12.type (), Variant::type_real, "42 - 3.14 --> real"); - t.is (v12.get_real (), 38.86, EPSILON, "42 - 3.14 --> 38.86"); - - // integer - string -> ERROR - try {Variant v13 = v1 - v3; t.fail ("42 - foo --> error");} - catch (...) {t.pass ("42 - foo --> error");} - - // integer - date -> date - Variant v1a (1300000000); - Variant v14 = v1a - v4; - t.is (v14.type (), Variant::type_date, "1300000000 - 1234567890 --> date"); - t.is (v14.get_date (), 65432110, "1300000000 - 1234567890 --> 65432110"); - - // integer - duration -> duration - Variant v15 = v1a - v5; - t.is (v15.type (), Variant::type_duration, "1300000000 - 1200 --> duration"); - t.is (v15.get_duration (), 1299998800, "1300000000 - 1200 --> 1299998800"); - - // real - boolean -> real - Variant v20 = v2 - v0; - t.is (v20.type (), Variant::type_real, "3.14 - true --> real"); - t.is (v20.get_real (), 2.14, EPSILON, "3.14 - true --> 2.14"); - - // real - integer -> real - Variant v21 = v2 - v1; - t.is (v21.type (), Variant::type_real, "3.14 - 42 --> real"); - t.is (v21.get_real (), -38.86, EPSILON, "3.14 - 42 --> -38.86"); - - // real - real -> real - Variant v22 = v2 - v2; - t.is (v22.type (), Variant::type_real, "3.14 - 3.14 --> real"); - t.is (v22.get_real (), 0.0, EPSILON, "3.14 - 3.14 --> 0.0"); - - // real - string -> ERROR - try {Variant v23 = v1 - v3; t.fail ("3.14 - foo --> error");} - catch (...) {t.pass ("3.14 - foo --> error");} - - // real - date -> real - Variant v2a (1300000000.0); - Variant v24 = v2a - v4; - t.is (v24.type (), Variant::type_real, "1300000000.0 - 1234567890 --> real"); - t.is (v24.get_real (), 65432110.0, "1300000000.0 - 1234567890 --> 65432110"); - - // real - duration -> real - Variant v25 = v2a - v5; - t.is (v25.type (), Variant::type_real, "1300000000.0 - 1200 --> real"); - t.is (v25.get_real (), 1299998800.0, "1300000000.0 - 1200 --> 1299998800"); - - // string - boolean -> ERROR - try {Variant v30 = v3 - v0; t.fail ("foo - foo --> error");} - catch (...) {t.pass ("foo - foo --> error");} - - // string - integer -> ERROR - try {Variant v31 = v3 - v1; t.fail ("foo - 42 --> error");} - catch (...) {t.pass ("foo - 42 --> error");} - - // string - real -> ERROR - try {Variant v32 = v3 - v2; t.fail ("foo - 3.14 --> error");} - catch (...) {t.pass ("foo - 3.14 --> error");} - - // string - string -> concatenated string - Variant v33 = v3 - v3; - t.is (v33.type (), Variant::type_string, "foo - foo --> string"); - t.is (v33.get_string (), "foo-foo", "foo - foo --> foo-foo"); - - // string - date -> ERROR - try {Variant v34 = v3 - v4; t.fail ("foo - 1234567890 --> error");} - catch (...) {t.pass ("foo - 1234567890 --> error");} - - // string - duration -> ERROR - try {Variant v35 = v3 - v5; t.fail ("foo - 1200 --> error");} - catch (...) {t.pass ("foo - 1200 --> error");} - - // date - boolean -> date - Variant v40 = v4 - v0; - t.is (v40.type (), Variant::type_date, "1234567890 - true --> date"); - t.is (v40.get_date (), 1234567889, "1234567890 - true --> 1234567889"); - - // date - integer -> date - Variant v41 = v4 - v1; - t.is (v41.type (), Variant::type_date, "1234567890 - 42 --> date"); - t.is (v41.get_date (), 1234567848, "1234567890 - 42 --> 1234567848"); - - // date - real -> date - Variant v42 = v4 - v2; - t.is (v42.type (), Variant::type_date, "1234567890 - 3.14 --> date"); - t.is (v42.get_date (), 1234567887, "1234567890 - 3.14 --> 1234567887"); - - // date - string -> string - try {Variant v43 = v4 - v3; t.fail ("1234567890 - foo --> error");} - catch (...) {t.pass ("1234567890 - foo --> error");} - - // date - date -> duration - Variant v44 = v4 - v4; - t.is (v44.type (), Variant::type_duration, "1234567890 - 1234567890 --> duration"); - t.is (v44.get_duration (), 0, "1234567890 - 1234567890 --> 0"); - - // date - duration -> date - Variant v45 = v4 - v5; - t.is (v45.type (), Variant::type_date, "1234567890 - 1200 --> date"); - t.is (v45.get_date (), 1234566690, "1234567890 - 1200 --> 1234566690"); - - // duration - boolean -> duration - Variant v50 = v5 - v0; - t.is (v50.type (), Variant::type_duration, "1200 - true --> duration"); - t.is (v50.get_duration (), 1199, "1200 - true --> 1199"); - - // duration - integer -> duration - Variant v51 = v5 - v1; - t.is (v51.type (), Variant::type_duration, "1200 - 42 --> duration"); - t.is (v51.get_duration (), 1158, "1200 - 42 --> 1158"); - - // duration - real -> duration - Variant v52 = v5 - v2; - t.is (v52.type (), Variant::type_duration, "1200 - 3.14 --> duration"); - t.is (v52.get_duration (), 1197, "1200 - 3.14 --> 1197"); - - // duration - string -> ERROR - try {Variant v53 = v5 - v3; t.fail ("1200 - foo --> error");} - catch (...) {t.pass ("1200 - foo --> error");} - - // duration - date -> ERROR - try {Variant v54 = v5 - v4; t.fail ("1200 - 1234567890 --> error");} - catch (...) {t.pass ("1200 - 1234567890 --> error");} - - // duration - duration -> duration - Variant v55 = v5 - v5; - t.is (v55.type (), Variant::type_duration, "1200 - 1200 --> duration"); - t.is (v55.get_duration (), 0, "1200 - 1200 --> 0"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_subtract_test.cpp b/test/variant_subtract_test.cpp new file mode 100644 index 000000000..967589123 --- /dev/null +++ b/test/variant_subtract_test.cpp @@ -0,0 +1,281 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +#define EPSILON 0.001 + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(55); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // boolean - boolean -> ERROR + try { + Variant v00 = v0 - v0; + t.fail("true - true --> error"); + } catch (...) { + t.pass("true - true --> error"); + } + + // boolean - integer -> ERROR + try { + Variant v01 = v0 - v1; + t.fail("true - 42 --> error"); + } catch (...) { + t.pass("true - 42 --> error"); + } + + // boolean - real -> ERROR + try { + Variant v02 = v0 - v2; + t.fail("true - 3.14 --> error"); + } catch (...) { + t.pass("true - 3.14 --> error"); + } + + // boolean - string -> ERROR + try { + Variant v03 = v0 - v3; + t.fail("true - foo --> error"); + } catch (...) { + t.pass("true - foo --> error"); + } + + // boolean - date -> ERROR + try { + Variant v04 = v0 - v4; + t.fail("true - 1234567890 --> error"); + } catch (...) { + t.pass("true - 1234567890 --> error"); + } + + // boolean - duration -> ERROR + try { + Variant v05 = v0 - v5; + t.fail("true - 1200 --> error"); + } catch (...) { + t.pass("true - 1200 --> error"); + } + + // integer - boolean -> integer + Variant v10 = v1 - v0; + t.is(v10.type(), Variant::type_integer, "42 - true --> integer"); + t.is(v10.get_integer(), 41, "42 - true --> 41"); + + // integer - integer -> integer + Variant v11 = v1 - v1; + t.is(v11.type(), Variant::type_integer, "42 - 42 --> integer"); + t.is(v11.get_integer(), 0, "42 - 42 --> 0"); + + // integer - real -> real + Variant v12 = v1 - v2; + t.is(v12.type(), Variant::type_real, "42 - 3.14 --> real"); + t.is(v12.get_real(), 38.86, EPSILON, "42 - 3.14 --> 38.86"); + + // integer - string -> ERROR + try { + Variant v13 = v1 - v3; + t.fail("42 - foo --> error"); + } catch (...) { + t.pass("42 - foo --> error"); + } + + // integer - date -> date + Variant v1a(1300000000); + Variant v14 = v1a - v4; + t.is(v14.type(), Variant::type_date, "1300000000 - 1234567890 --> date"); + t.is(v14.get_date(), 65432110, "1300000000 - 1234567890 --> 65432110"); + + // integer - duration -> duration + Variant v15 = v1a - v5; + t.is(v15.type(), Variant::type_duration, "1300000000 - 1200 --> duration"); + t.is(v15.get_duration(), 1299998800, "1300000000 - 1200 --> 1299998800"); + + // real - boolean -> real + Variant v20 = v2 - v0; + t.is(v20.type(), Variant::type_real, "3.14 - true --> real"); + t.is(v20.get_real(), 2.14, EPSILON, "3.14 - true --> 2.14"); + + // real - integer -> real + Variant v21 = v2 - v1; + t.is(v21.type(), Variant::type_real, "3.14 - 42 --> real"); + t.is(v21.get_real(), -38.86, EPSILON, "3.14 - 42 --> -38.86"); + + // real - real -> real + Variant v22 = v2 - v2; + t.is(v22.type(), Variant::type_real, "3.14 - 3.14 --> real"); + t.is(v22.get_real(), 0.0, EPSILON, "3.14 - 3.14 --> 0.0"); + + // real - string -> ERROR + try { + Variant v23 = v1 - v3; + t.fail("3.14 - foo --> error"); + } catch (...) { + t.pass("3.14 - foo --> error"); + } + + // real - date -> real + Variant v2a(1300000000.0); + Variant v24 = v2a - v4; + t.is(v24.type(), Variant::type_real, "1300000000.0 - 1234567890 --> real"); + t.is(v24.get_real(), 65432110.0, "1300000000.0 - 1234567890 --> 65432110"); + + // real - duration -> real + Variant v25 = v2a - v5; + t.is(v25.type(), Variant::type_real, "1300000000.0 - 1200 --> real"); + t.is(v25.get_real(), 1299998800.0, "1300000000.0 - 1200 --> 1299998800"); + + // string - boolean -> ERROR + try { + Variant v30 = v3 - v0; + t.fail("foo - foo --> error"); + } catch (...) { + t.pass("foo - foo --> error"); + } + + // string - integer -> ERROR + try { + Variant v31 = v3 - v1; + t.fail("foo - 42 --> error"); + } catch (...) { + t.pass("foo - 42 --> error"); + } + + // string - real -> ERROR + try { + Variant v32 = v3 - v2; + t.fail("foo - 3.14 --> error"); + } catch (...) { + t.pass("foo - 3.14 --> error"); + } + + // string - string -> concatenated string + Variant v33 = v3 - v3; + t.is(v33.type(), Variant::type_string, "foo - foo --> string"); + t.is(v33.get_string(), "foo-foo", "foo - foo --> foo-foo"); + + // string - date -> ERROR + try { + Variant v34 = v3 - v4; + t.fail("foo - 1234567890 --> error"); + } catch (...) { + t.pass("foo - 1234567890 --> error"); + } + + // string - duration -> ERROR + try { + Variant v35 = v3 - v5; + t.fail("foo - 1200 --> error"); + } catch (...) { + t.pass("foo - 1200 --> error"); + } + + // date - boolean -> date + Variant v40 = v4 - v0; + t.is(v40.type(), Variant::type_date, "1234567890 - true --> date"); + t.is(v40.get_date(), 1234567889, "1234567890 - true --> 1234567889"); + + // date - integer -> date + Variant v41 = v4 - v1; + t.is(v41.type(), Variant::type_date, "1234567890 - 42 --> date"); + t.is(v41.get_date(), 1234567848, "1234567890 - 42 --> 1234567848"); + + // date - real -> date + Variant v42 = v4 - v2; + t.is(v42.type(), Variant::type_date, "1234567890 - 3.14 --> date"); + t.is(v42.get_date(), 1234567887, "1234567890 - 3.14 --> 1234567887"); + + // date - string -> string + try { + Variant v43 = v4 - v3; + t.fail("1234567890 - foo --> error"); + } catch (...) { + t.pass("1234567890 - foo --> error"); + } + + // date - date -> duration + Variant v44 = v4 - v4; + t.is(v44.type(), Variant::type_duration, "1234567890 - 1234567890 --> duration"); + t.is(v44.get_duration(), 0, "1234567890 - 1234567890 --> 0"); + + // date - duration -> date + Variant v45 = v4 - v5; + t.is(v45.type(), Variant::type_date, "1234567890 - 1200 --> date"); + t.is(v45.get_date(), 1234566690, "1234567890 - 1200 --> 1234566690"); + + // duration - boolean -> duration + Variant v50 = v5 - v0; + t.is(v50.type(), Variant::type_duration, "1200 - true --> duration"); + t.is(v50.get_duration(), 1199, "1200 - true --> 1199"); + + // duration - integer -> duration + Variant v51 = v5 - v1; + t.is(v51.type(), Variant::type_duration, "1200 - 42 --> duration"); + t.is(v51.get_duration(), 1158, "1200 - 42 --> 1158"); + + // duration - real -> duration + Variant v52 = v5 - v2; + t.is(v52.type(), Variant::type_duration, "1200 - 3.14 --> duration"); + t.is(v52.get_duration(), 1197, "1200 - 3.14 --> 1197"); + + // duration - string -> ERROR + try { + Variant v53 = v5 - v3; + t.fail("1200 - foo --> error"); + } catch (...) { + t.pass("1200 - foo --> error"); + } + + // duration - date -> ERROR + try { + Variant v54 = v5 - v4; + t.fail("1200 - 1234567890 --> error"); + } catch (...) { + t.pass("1200 - 1234567890 --> error"); + } + + // duration - duration -> duration + Variant v55 = v5 - v5; + t.is(v55.type(), Variant::type_duration, "1200 - 1200 --> duration"); + t.is(v55.get_duration(), 0, "1200 - 1200 --> 0"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_xor.t.cpp b/test/variant_xor.t.cpp deleted file mode 100644 index 54a4f6a0f..000000000 --- a/test/variant_xor.t.cpp +++ /dev/null @@ -1,199 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2013 - 2021, Göteborg Bit Factory. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (76); - - Variant v0 (true); - Variant v1 (42); - Variant v2 (3.14); - Variant v3 ("foo"); - Variant v4 (1234567890, Variant::type_date); - Variant v5 (1200, Variant::type_duration); - - // Truth table. - Variant vFalse (false); - Variant vTrue (true); - t.is (vFalse.operator_xor (vFalse), false, "false xor false --> false"); - t.is (vFalse.operator_xor (vTrue), true, "false xor true --> false"); - t.is (vTrue.operator_xor (vFalse), true, "true xor false --> false"); - t.is (vTrue.operator_xor (vTrue), false, "true xor true --> false"); - - Variant v00 = v0.operator_xor (v0); - t.is (v00.type (), Variant::type_boolean, "true xor true --> boolean"); - t.is (v00.get_bool (), false, "true xor true --> false"); - - Variant v01 = v0.operator_xor (v1); - t.is (v01.type (), Variant::type_boolean, "true xor 42 --> boolean"); - t.is (v01.get_bool (), false, "true xor 42 --> false"); - - Variant v02 = v0.operator_xor (v2); - t.is (v02.type (), Variant::type_boolean, "true xor 3.14 --> boolean"); - t.is (v02.get_bool (), false, "true xor 3.14 --> false"); - - Variant v03 = v0.operator_xor (v3); - t.is (v03.type (), Variant::type_boolean, "true xor 'foo' --> boolean"); - t.is (v03.get_bool (), false, "true xor 'foo' --> false"); - - Variant v04 = v0.operator_xor (v4); - t.is (v04.type (), Variant::type_boolean, "true xor 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "true xor 1234567890 --> false"); - - Variant v05 = v0.operator_xor (v5); - t.is (v05.type (), Variant::type_boolean, "true xor 1200 --> boolean"); - t.is (v05.get_bool (), false, "true xor 1200 --> false"); - - Variant v10 = v1.operator_xor (v0); - t.is (v10.type (), Variant::type_boolean, "42 xor true --> boolean"); - t.is (v10.get_bool (), false, "42 xor true --> false"); - - Variant v11 = v1.operator_xor (v1); - t.is (v11.type (), Variant::type_boolean, "42 xor 42 --> boolean"); - t.is (v11.get_bool (), false, "42 xor 42 --> false"); - - Variant v12 = v1.operator_xor (v2); - t.is (v12.type (), Variant::type_boolean, "42 xor 3.14 --> boolean"); - t.is (v12.get_bool (), false, "42 xor 3.14 --> false"); - - Variant v13 = v1.operator_xor (v3); - t.is (v13.type (), Variant::type_boolean, "42 xor 'foo' --> boolean"); - t.is (v13.get_bool (), false, "42 xor 'foo' --> false"); - - Variant v14 = v1.operator_xor (v4); - t.is (v04.type (), Variant::type_boolean, "42 xor 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "42 xor 1234567890 --> false"); - - Variant v15 = v1.operator_xor (v5); - t.is (v15.type (), Variant::type_boolean, "42 xor 1200 --> boolean"); - t.is (v15.get_bool (), false, "42 xor 1200 --> false"); - - Variant v20 = v2.operator_xor (v0); - t.is (v20.type (), Variant::type_boolean, "3.14 xor true --> boolean"); - t.is (v20.get_bool (), false, "3.14 xor true --> false"); - - Variant v21 = v2.operator_xor (v1); - t.is (v21.type (), Variant::type_boolean, "3.14 xor 42 --> boolean"); - t.is (v21.get_bool (), false, "3.14 xor 42 --> false"); - - Variant v22 = v2.operator_xor (v2); - t.is (v22.type (), Variant::type_boolean, "3.14 xor 3.14 --> boolean"); - t.is (v22.get_bool (), false, "3.14 xor 3.14 --> false"); - - Variant v23 = v2.operator_xor (v3); - t.is (v23.type (), Variant::type_boolean, "3.14 xor 'foo' --> boolean"); - t.is (v23.get_bool (), false, "3.14 xor 'foo' --> false"); - - Variant v24 = v2.operator_xor (v4); - t.is (v24.type (), Variant::type_boolean, "3.14 xor 1234567890 --> boolean"); - t.is (v24.get_bool (), false, "3.14 xor 1234567890 --> false"); - - Variant v25 = v2.operator_xor (v5); - t.is (v25.type (), Variant::type_boolean, "3.14 xor 1200 --> boolean"); - t.is (v25.get_bool (), false, "3.14 xor 1200 --> false"); - - Variant v30 = v3.operator_xor (v0); - t.is (v30.type (), Variant::type_boolean, "'foo' xor true --> boolean"); - t.is (v30.get_bool (), false, "'foo' xor true --> false"); - - Variant v31 = v3.operator_xor (v1); - t.is (v31.type (), Variant::type_boolean, "'foo' xor 42 --> boolean"); - t.is (v31.get_bool (), false, "'foo' xor 42 --> false"); - - Variant v32 = v3.operator_xor (v2); - t.is (v32.type (), Variant::type_boolean, "'foo' xor 3.14 --> boolean"); - t.is (v32.get_bool (), false, "'foo' xor 3.14 --> false"); - - Variant v33 = v3.operator_xor (v3); - t.is (v33.type (), Variant::type_boolean, "'foo' xor 'foo' --> boolean"); - t.is (v33.get_bool (), false, "'foo' xor 'foo' --> false"); - - Variant v34 = v3.operator_xor (v4); - t.is (v34.type (), Variant::type_boolean, "'foo' xor 1234567890 --> boolean"); - t.is (v34.get_bool (), false, "'foo' xor 1234567890 --> false"); - - Variant v35 = v3.operator_xor (v5); - t.is (v35.type (), Variant::type_boolean, "'foo' xor 1200 --> boolean"); - t.is (v35.get_bool (), false, "'foo' xor 1200 --> false"); - - Variant v40 = v4.operator_xor (v0); - t.is (v40.type (), Variant::type_boolean, "1234567890 xor true --> boolean"); - t.is (v40.get_bool (), false, "1234567890 xor true --> false"); - - Variant v41 = v4.operator_xor (v1); - t.is (v41.type (), Variant::type_boolean, "1234567890 xor 42 --> boolean"); - t.is (v41.get_bool (), false, "1234567890 xor 42 --> false"); - - Variant v42 = v4.operator_xor (v2); - t.is (v42.type (), Variant::type_boolean, "1234567890 xor 3.14 --> boolean"); - t.is (v42.get_bool (), false, "1234567890 xor 3.14 --> false"); - - Variant v43 = v4.operator_xor (v3); - t.is (v43.type (), Variant::type_boolean, "1234567890 xor 'foo' --> boolean"); - t.is (v43.get_bool (), false, "1234567890 xor 'foo' --> false"); - - Variant v44 = v4.operator_xor (v4); - t.is (v44.type (), Variant::type_boolean, "1234567890 xor 1234567890 --> boolean"); - t.is (v44.get_bool (), false, "1234567890 xor 1234567890 --> false"); - - Variant v45 = v4.operator_xor (v5); - t.is (v45.type (), Variant::type_boolean, "1234567890 xor 1200 --> boolean"); - t.is (v45.get_bool (), false, "1234567890 xor 1200 --> false"); - - Variant v50 = v5.operator_xor (v0); - t.is (v50.type (), Variant::type_boolean, "1200 xor true --> boolean"); - t.is (v50.get_bool (), false, "1200 xor true --> false"); - - Variant v51 = v5.operator_xor (v1); - t.is (v51.type (), Variant::type_boolean, "1200 xor 42 --> boolean"); - t.is (v51.get_bool (), false, "1200 xor 42 --> false"); - - Variant v52 = v5.operator_xor (v2); - t.is (v52.type (), Variant::type_boolean, "1200 xor 3.14 --> boolean"); - t.is (v52.get_bool (), false, "1200 xor 3.14 --> false"); - - Variant v53 = v5.operator_xor (v3); - t.is (v53.type (), Variant::type_boolean, "1200 xor 'foo' --> boolean"); - t.is (v53.get_bool (), false, "1200 xor 'foo' --> false"); - - Variant v54 = v5.operator_xor (v4); - t.is (v04.type (), Variant::type_boolean, "1200 xor 1234567890 --> boolean"); - t.is (v04.get_bool (), false, "1200 xor 1234567890 --> false"); - - Variant v55 = v5.operator_xor (v5); - t.is (v55.type (), Variant::type_boolean, "1200 xor 1200 --> boolean"); - t.is (v55.get_bool (), false, "1200 xor 1200 --> false"); - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/variant_xor_test.cpp b/test/variant_xor_test.cpp new file mode 100644 index 000000000..0917d35a3 --- /dev/null +++ b/test/variant_xor_test.cpp @@ -0,0 +1,201 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2013 - 2021, Göteborg Bit Factory. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(76); + + Variant v0(true); + Variant v1(42); + Variant v2(3.14); + Variant v3("foo"); + Variant v4(1234567890, Variant::type_date); + Variant v5(1200, Variant::type_duration); + + // Truth table. + Variant vFalse(false); + Variant vTrue(true); + t.is(vFalse.operator_xor(vFalse), false, "false xor false --> false"); + t.is(vFalse.operator_xor(vTrue), true, "false xor true --> false"); + t.is(vTrue.operator_xor(vFalse), true, "true xor false --> false"); + t.is(vTrue.operator_xor(vTrue), false, "true xor true --> false"); + + Variant v00 = v0.operator_xor(v0); + t.is(v00.type(), Variant::type_boolean, "true xor true --> boolean"); + t.is(v00.get_bool(), false, "true xor true --> false"); + + Variant v01 = v0.operator_xor(v1); + t.is(v01.type(), Variant::type_boolean, "true xor 42 --> boolean"); + t.is(v01.get_bool(), false, "true xor 42 --> false"); + + Variant v02 = v0.operator_xor(v2); + t.is(v02.type(), Variant::type_boolean, "true xor 3.14 --> boolean"); + t.is(v02.get_bool(), false, "true xor 3.14 --> false"); + + Variant v03 = v0.operator_xor(v3); + t.is(v03.type(), Variant::type_boolean, "true xor 'foo' --> boolean"); + t.is(v03.get_bool(), false, "true xor 'foo' --> false"); + + Variant v04 = v0.operator_xor(v4); + t.is(v04.type(), Variant::type_boolean, "true xor 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "true xor 1234567890 --> false"); + + Variant v05 = v0.operator_xor(v5); + t.is(v05.type(), Variant::type_boolean, "true xor 1200 --> boolean"); + t.is(v05.get_bool(), false, "true xor 1200 --> false"); + + Variant v10 = v1.operator_xor(v0); + t.is(v10.type(), Variant::type_boolean, "42 xor true --> boolean"); + t.is(v10.get_bool(), false, "42 xor true --> false"); + + Variant v11 = v1.operator_xor(v1); + t.is(v11.type(), Variant::type_boolean, "42 xor 42 --> boolean"); + t.is(v11.get_bool(), false, "42 xor 42 --> false"); + + Variant v12 = v1.operator_xor(v2); + t.is(v12.type(), Variant::type_boolean, "42 xor 3.14 --> boolean"); + t.is(v12.get_bool(), false, "42 xor 3.14 --> false"); + + Variant v13 = v1.operator_xor(v3); + t.is(v13.type(), Variant::type_boolean, "42 xor 'foo' --> boolean"); + t.is(v13.get_bool(), false, "42 xor 'foo' --> false"); + + Variant v14 = v1.operator_xor(v4); + t.is(v04.type(), Variant::type_boolean, "42 xor 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "42 xor 1234567890 --> false"); + + Variant v15 = v1.operator_xor(v5); + t.is(v15.type(), Variant::type_boolean, "42 xor 1200 --> boolean"); + t.is(v15.get_bool(), false, "42 xor 1200 --> false"); + + Variant v20 = v2.operator_xor(v0); + t.is(v20.type(), Variant::type_boolean, "3.14 xor true --> boolean"); + t.is(v20.get_bool(), false, "3.14 xor true --> false"); + + Variant v21 = v2.operator_xor(v1); + t.is(v21.type(), Variant::type_boolean, "3.14 xor 42 --> boolean"); + t.is(v21.get_bool(), false, "3.14 xor 42 --> false"); + + Variant v22 = v2.operator_xor(v2); + t.is(v22.type(), Variant::type_boolean, "3.14 xor 3.14 --> boolean"); + t.is(v22.get_bool(), false, "3.14 xor 3.14 --> false"); + + Variant v23 = v2.operator_xor(v3); + t.is(v23.type(), Variant::type_boolean, "3.14 xor 'foo' --> boolean"); + t.is(v23.get_bool(), false, "3.14 xor 'foo' --> false"); + + Variant v24 = v2.operator_xor(v4); + t.is(v24.type(), Variant::type_boolean, "3.14 xor 1234567890 --> boolean"); + t.is(v24.get_bool(), false, "3.14 xor 1234567890 --> false"); + + Variant v25 = v2.operator_xor(v5); + t.is(v25.type(), Variant::type_boolean, "3.14 xor 1200 --> boolean"); + t.is(v25.get_bool(), false, "3.14 xor 1200 --> false"); + + Variant v30 = v3.operator_xor(v0); + t.is(v30.type(), Variant::type_boolean, "'foo' xor true --> boolean"); + t.is(v30.get_bool(), false, "'foo' xor true --> false"); + + Variant v31 = v3.operator_xor(v1); + t.is(v31.type(), Variant::type_boolean, "'foo' xor 42 --> boolean"); + t.is(v31.get_bool(), false, "'foo' xor 42 --> false"); + + Variant v32 = v3.operator_xor(v2); + t.is(v32.type(), Variant::type_boolean, "'foo' xor 3.14 --> boolean"); + t.is(v32.get_bool(), false, "'foo' xor 3.14 --> false"); + + Variant v33 = v3.operator_xor(v3); + t.is(v33.type(), Variant::type_boolean, "'foo' xor 'foo' --> boolean"); + t.is(v33.get_bool(), false, "'foo' xor 'foo' --> false"); + + Variant v34 = v3.operator_xor(v4); + t.is(v34.type(), Variant::type_boolean, "'foo' xor 1234567890 --> boolean"); + t.is(v34.get_bool(), false, "'foo' xor 1234567890 --> false"); + + Variant v35 = v3.operator_xor(v5); + t.is(v35.type(), Variant::type_boolean, "'foo' xor 1200 --> boolean"); + t.is(v35.get_bool(), false, "'foo' xor 1200 --> false"); + + Variant v40 = v4.operator_xor(v0); + t.is(v40.type(), Variant::type_boolean, "1234567890 xor true --> boolean"); + t.is(v40.get_bool(), false, "1234567890 xor true --> false"); + + Variant v41 = v4.operator_xor(v1); + t.is(v41.type(), Variant::type_boolean, "1234567890 xor 42 --> boolean"); + t.is(v41.get_bool(), false, "1234567890 xor 42 --> false"); + + Variant v42 = v4.operator_xor(v2); + t.is(v42.type(), Variant::type_boolean, "1234567890 xor 3.14 --> boolean"); + t.is(v42.get_bool(), false, "1234567890 xor 3.14 --> false"); + + Variant v43 = v4.operator_xor(v3); + t.is(v43.type(), Variant::type_boolean, "1234567890 xor 'foo' --> boolean"); + t.is(v43.get_bool(), false, "1234567890 xor 'foo' --> false"); + + Variant v44 = v4.operator_xor(v4); + t.is(v44.type(), Variant::type_boolean, "1234567890 xor 1234567890 --> boolean"); + t.is(v44.get_bool(), false, "1234567890 xor 1234567890 --> false"); + + Variant v45 = v4.operator_xor(v5); + t.is(v45.type(), Variant::type_boolean, "1234567890 xor 1200 --> boolean"); + t.is(v45.get_bool(), false, "1234567890 xor 1200 --> false"); + + Variant v50 = v5.operator_xor(v0); + t.is(v50.type(), Variant::type_boolean, "1200 xor true --> boolean"); + t.is(v50.get_bool(), false, "1200 xor true --> false"); + + Variant v51 = v5.operator_xor(v1); + t.is(v51.type(), Variant::type_boolean, "1200 xor 42 --> boolean"); + t.is(v51.get_bool(), false, "1200 xor 42 --> false"); + + Variant v52 = v5.operator_xor(v2); + t.is(v52.type(), Variant::type_boolean, "1200 xor 3.14 --> boolean"); + t.is(v52.get_bool(), false, "1200 xor 3.14 --> false"); + + Variant v53 = v5.operator_xor(v3); + t.is(v53.type(), Variant::type_boolean, "1200 xor 'foo' --> boolean"); + t.is(v53.get_bool(), false, "1200 xor 'foo' --> false"); + + Variant v54 = v5.operator_xor(v4); + t.is(v04.type(), Variant::type_boolean, "1200 xor 1234567890 --> boolean"); + t.is(v04.get_bool(), false, "1200 xor 1234567890 --> false"); + + Variant v55 = v5.operator_xor(v5); + t.is(v55.type(), Variant::type_boolean, "1200 xor 1200 --> boolean"); + t.is(v55.get_bool(), false, "1200 xor 1200 --> false"); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/verbose.t b/test/verbose.test.py similarity index 91% rename from test/verbose.t rename to test/verbose.test.py index 7677d8550..06b8b69bf 100755 --- a/test/verbose.t +++ b/test/verbose.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -58,14 +57,16 @@ class TestVerbosity(TestCase): def test_verbosity_new_uuid(self): """Verbosity new-uuid""" code, out, err = self.t(("rc.verbose:new-uuid", "add", "Sample1")) - self.assertRegex(out, r"Created task [0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}") + self.assertRegex( + out, + r"Created task [0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", + ) def test_verbosity_label(self): """Verbosity label""" code, out, err = self.t("rc.verbose:label ls") self.assertRegex( - out, - "ID.+A.+D.+Project.+Tags.+R.+Wait.+S.+Due.+Until.+Description" + out, "ID.+A.+D.+Project.+Tags.+R.+Wait.+S.+Due.+Until.+Description" ) def test_verbosity_affected(self): @@ -87,14 +88,17 @@ class TestVerbosity(TestCase): """Verbosity special""" code, out, err = self.t("rc.verbose:special 1 mod +next") - self.assertIn("The 'next' special tag will boost the urgency of this " - "task so it appears on the 'next' report.", out) + self.assertIn( + "The 'next' special tag will boost the urgency of this " + "task so it appears on the 'next' report.", + out, + ) def test_verbosity_blank(self): """Verbosity blank""" def count_blank_lines(x): - return x.splitlines().count('') + return x.splitlines().count("") code, out, err = self.t("rc.verbose:nothing ls") self.assertEqual(count_blank_lines(out), 0) @@ -143,6 +147,7 @@ class TestVerbosity(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/version.t b/test/version.test.py similarity index 82% rename from test/version.t rename to test/version.test.py index 2e4dfc5a0..6d751a643 100755 --- a/test/version.t +++ b/test/version.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -31,11 +30,12 @@ import os import unittest import re from datetime import datetime + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) from basetest import Task, TestCase -from basetest.utils import run_cmd_wait +from basetest.utils import run_cmd_wait, SOURCE_DIR class TestVersion(TestCase): @@ -54,18 +54,17 @@ class TestVersion(TestCase): """Copyright is current""" code, out, err = self.t("version") - expected = "Copyright \(C\) \d{4} - %d" % (datetime.now().year,) + expected = r"Copyright \(C\) \d{4} - %d" % (datetime.now().year,) self.assertRegex(out, expected) - def slurp(self, file="../CMakeLists.txt"): + def slurp(self, file=os.path.join(SOURCE_DIR, "CMakeLists.txt")): number = "\.".join(["[0-9]+"] * 3) - ver = re.compile("^set \(PROJECT_VERSION \"({0}[^\"]*)\"\)$".format(number)) + ver = re.compile('^ *VERSION ({0}[^"]*)$'.format(number)) with open(file) as fh: for line in fh: - if "PROJECT_VERSION" in line: - match = ver.match(line) - if match: - return match.group(1) + match = ver.match(line) + if match: + return match.group(1).strip() raise ValueError("Couldn't find matching version in {0}".format(file)) def test_version(self): @@ -87,12 +86,11 @@ class TestVersion(TestCase): # If we are within a git repository, check the tag version if os.path.exists("../.git"): - if 2 >= len(version) > 0: - git = version[1] - self.assertRegex(git, r'\([a-f0-9]*\)')) - else: - raise ValueError("Unexpected output from _version '{0}'".format( - out)) + if 2 >= len(version) > 0: + git = version[1] + self.assertRegex(git, r"\([a-f0-9]*\)") + else: + raise ValueError("Unexpected output from _version '{0}'".format(out)) ver = version[0] ver_expected = self.slurp() @@ -104,11 +102,12 @@ class TestVersion(TestCase): def test_version_option(self): """Verify that 'task --version' returns something valid""" code, out, err = self.t("--version") - self.assertRegex(out, r'^\d\.\d+\.\d+(\.\w+)?$') + self.assertRegex(out, r"^\d\.\d+\.\d+(\.\w+)?$") if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python diff --git a/test/view.t.cpp b/test/view.t.cpp deleted file mode 100644 index cdf4b4685..000000000 --- a/test/view.t.cpp +++ /dev/null @@ -1,159 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. -// -// 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. -// -// https://www.opensource.org/licenses/mit-license.php -// -//////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -Context context; -extern std::string configurationDefaults; - -//////////////////////////////////////////////////////////////////////////////// -int main (int, char**) -{ - UnitTest t (1); - - // Ensure environment has no influence. - unsetenv ("TASKDATA"); - unsetenv ("TASKRC"); - - try - { - // Set up configuration. - context.config.parse (configurationDefaults); - context.config.set ("fontunderline", true); - context.config.set ("tag.indicator", "+"); - context.config.set ("dependency.indicator", "D"); - context.config.set ("recurrence.indicator", "R"); - context.config.set ("active.indicator", "A"); - context.config.set ("dateformat", "Y-M-D"); - context.config.set ("indent.annotation", "2"); - - // Two sample tasks. - Task t1 ("[" - "status:\"pending\" " - "uuid:\"2a64f6e0-bf8e-430d-bf71-9ec3f0d9b661\" " - "description:\"This is the description text\" " - "project:\"Home\" " - "priority:\"H\" " - "annotation_1234567890:\"This is an annotation\" " - "start:\"1234567890\" " - "due:\"1234567890\" " - "tags:\"one,two\"" - "]"); - t1.id = 1; - Task t2 ("[" - "status:\"pending\" " - "uuid:\"f30cb9c3-3fc0-483f-bfb2-3bf134f00694\" " - "description:\"This is the description text\" " - "project:\"Garden Care\" " - "recur:\"monthly\" " - "depends:\"2a64f6e0-bf8e-430d-bf71-9ec3f0d9b661\"" - "]"); - t2.id = 11; - Task t3 ("[" - "status:\"pending\" " - "uuid:\"c44cb9c3-3fc0-483f-bfb2-3bf134f05554\" " - "description:\"Another description\" " - "project:\"Garden\" " - "]"); - t3.id = 8; - - std::vector data; - data.push_back (t1); - data.push_back (t2); - data.push_back (t3); - - // Sequence of tasks. - std::vector sequence; - sequence.push_back (0); - sequence.push_back (1); - sequence.push_back (2); - - sort_tasks (data, sequence, "description+,id-"); - - // Create colors. - Color header_color (Color (Color::yellow, Color::nocolor, false, false, false)); - Color odd_color ("on gray1"); - Color even_color ("on gray0"); - - // Create a view. - std::string report = "view.t"; - ViewTask view; - view.add (Column::factory ("id", report)); - view.add (Column::factory ("uuid.short", report)); - view.add (Column::factory ("project", report)); - view.add (Column::factory ("priority", report)); - view.add (Column::factory ("tags", report)); - view.add (Column::factory ("tags.count", report)); - view.add (Column::factory ("description", report)); - view.add (Column::factory ("depends.indicator", report)); - view.add (Column::factory ("recur.indicator", report)); - view.add (Column::factory ("status.short", report)); - view.add (Column::factory ("due.countdown", report)); - view.add (Column::factory ("start.active", report)); - view.add (Column::factory ("urgency", report)); - view.width (context.getWidth ()); - view.leftMargin (4); - view.extraPadding (0); - view.intraPadding (1); - view.colorHeader (header_color); - view.colorOdd (odd_color); - view.colorEven (even_color); - view.intraColorOdd (odd_color); - view.intraColorEven (even_color); - - // Render the view. - std::cout << view.render (data, sequence); - int expected_lines = 5; - if (!isatty (fileno (stdout))) - expected_lines = 6; - - t.is (view.lines (), expected_lines, "View::lines == 5"); - - // Now render a string-only grid. - context.config.set ("fontunderline", false); - Color single_cell ("bold white on red"); - } - - catch (const std::string& e) - { - t.fail ("Exception thrown."); - t.diag (e); - } - - return 0; -} - -//////////////////////////////////////////////////////////////////////////////// diff --git a/test/view_test.cpp b/test/view_test.cpp new file mode 100644 index 000000000..09b0c774c --- /dev/null +++ b/test/view_test.cpp @@ -0,0 +1,167 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. +// +// 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. +// +// https://www.opensource.org/licenses/mit-license.php +// +//////////////////////////////////////////////////////////////////////////////// + +#include +// cmake.h include header must come first + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +Context context; +extern std::string configurationDefaults; + +//////////////////////////////////////////////////////////////////////////////// +int TEST_NAME(int, char**) { + UnitTest t(1); + Context context; + Context::setContext(&context); + context.tdb2.open_replica_in_memory(); + + // Ensure environment has no influence. + unsetenv("TASKDATA"); + unsetenv("TASKRC"); + + try { + // Set up configuration. + context.config.parse(configurationDefaults); + context.config.set("fontunderline", true); + context.config.set("tag.indicator", "+"); + context.config.set("dependency.indicator", "D"); + context.config.set("recurrence.indicator", "R"); + context.config.set("active.indicator", "A"); + context.config.set("dateformat", "Y-M-D"); + context.config.set("indent.annotation", "2"); + + // Two sample tasks. + t.ok(true, "zero"); + Task t1( + "{" + "\"status\":\"pending\", " + "\"uuid\":\"2a64f6e0-bf8e-430d-bf71-9ec3f0d9b661\", " + "\"description\":\"This is the description text\", " + "\"project\":\"Home\", " + "\"priority\":\"H\", " + "\"annotation_1234567890\":\"This is an annotation\", " + "\"start\":\"1234567890\", " + "\"due\":\"1234567890\", " + "\"tags\":\"one,two\"" + "}"); + t1.id = 1; + t.ok(true, "one"); + Task t2( + "{" + "\"status\":\"pending\", " + "\"uuid\":\"f30cb9c3-3fc0-483f-bfb2-3bf134f00694\", " + "\"description\":\"This is the description text\", " + "\"project\":\"Garden Care\", " + "\"recur\":\"monthly\", " + "\"depends\":\"2a64f6e0-bf8e-430d-bf71-9ec3f0d9b661\"" + "}"); + t2.id = 11; + t.ok(true, "two"); + Task t3( + "{" + "\"status\":\"pending\", " + "\"uuid\":\"c44cb9c3-3fc0-483f-bfb2-3bf134f05554\", " + "\"description\":\"Another description\", " + "\"project\":\"Garden\"" + "}"); + t3.id = 8; + t.ok(true, "three"); + + std::vector data; + data.push_back(t1); + data.push_back(t2); + data.push_back(t3); + + // Sequence of tasks. + std::vector sequence; + sequence.push_back(0); + sequence.push_back(1); + sequence.push_back(2); + + sort_tasks(data, sequence, "description+,id-"); + + // Create colors. + Color header_color(Color(Color::yellow, Color::nocolor, false, false, false)); + Color odd_color("on gray1"); + Color even_color("on gray0"); + + // Create a view. + std::string report = "view.t"; + ViewTask view; + view.add(Column::factory("id", report)); + view.add(Column::factory("uuid.short", report)); + view.add(Column::factory("project", report)); + view.add(Column::factory("priority", report)); + view.add(Column::factory("tags", report)); + view.add(Column::factory("tags.count", report)); + view.add(Column::factory("description", report)); + view.add(Column::factory("depends.indicator", report)); + view.add(Column::factory("recur.indicator", report)); + view.add(Column::factory("status.short", report)); + view.add(Column::factory("due.countdown", report)); + view.add(Column::factory("start.active", report)); + view.add(Column::factory("urgency", report)); + view.width(context.getWidth()); + view.leftMargin(4); + view.extraPadding(0); + view.intraPadding(1); + view.colorHeader(header_color); + view.colorOdd(odd_color); + view.colorEven(even_color); + view.intraColorOdd(odd_color); + view.intraColorEven(even_color); + + // Render the view. + std::cout << view.render(data, sequence); + int expected_lines = 5; + if (!isatty(fileno(stdout))) expected_lines = 6; + + t.is(view.lines(), expected_lines, "View::lines == 5"); + + // Now render a string-only grid. + context.config.set("fontunderline", false); + Color single_cell("bold white on red"); + } + + catch (const std::string& e) { + t.fail("Exception thrown."); + t.diag(e); + } + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/test/wait.t b/test/wait.test.py similarity index 89% rename from test/wait.t rename to test/wait.test.py index a85dd5de9..adc609730 100755 --- a/test/wait.t +++ b/test/wait.test.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- ############################################################################### # # Copyright 2006 - 2021, Tomas Babej, Paul Beckingham, Federico Hernandez. @@ -29,6 +28,7 @@ import sys import os import unittest + # Ensure python finds the local simpletap module sys.path.append(os.path.dirname(os.path.abspath(__file__))) @@ -87,19 +87,19 @@ class Test1486(TestCase): def test_waiting(self): """1486: Verify waiting report shows waiting tasks""" - self.t('add regular') - self.t('add waited and pending wait:later') - self.t('add waited but deleted wait:later') - self.t('add waited but done wait:later') - self.t('rc.confirmation=off 3 delete') - self.t('4 done') + self.t("add regular") + self.t("add waited and pending wait:later") + self.t("add waited but deleted wait:later") + self.t("add waited but done wait:later") + self.t("rc.confirmation=off 3 delete") + self.t("4 done") - code, out, err = self.t('waiting') + code, out, err = self.t("waiting") self.assertEqual(0, code, "Exit code was non-zero ({0})".format(code)) - self.assertIn('waited and pending', out) - self.assertNotIn('waited but deleted', out) - self.assertNotIn('waited but done', out) - self.assertNotIn('regular', out) + self.assertIn("waited and pending", out) + self.assertNotIn("waited but deleted", out) + self.assertNotIn("waited but done", out) + self.assertNotIn("regular", out) class TestFeature2563(TestCase): @@ -130,6 +130,7 @@ class TestFeature2563(TestCase): if __name__ == "__main__": from simpletap import TAPTestRunner + unittest.main(testRunner=TAPTestRunner()) # vim: ai sts=4 et sw=4 ft=python