mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Compare commits
243 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c594ecb58d | ||
![]() |
baaf69202b | ||
![]() |
a949c698f9 | ||
![]() |
ffa0d3e944 | ||
![]() |
6d81c8cda0 | ||
![]() |
440d3f8c92 | ||
![]() |
e5b69afee2 | ||
![]() |
75d351afad | ||
![]() |
f6824e90a1 | ||
![]() |
89d84f0bdd | ||
![]() |
4620b5fd25 | ||
![]() |
6c60a8db84 | ||
![]() |
79eb38d582 | ||
![]() |
0e59a62ead | ||
![]() |
97bcc76ac1 | ||
![]() |
499f931f67 | ||
![]() |
416c6d3ca4 | ||
![]() |
36e5f8895d | ||
![]() |
b4e25fe42f | ||
![]() |
7be313e91f | ||
![]() |
36a449c935 | ||
![]() |
31829d61fc | ||
![]() |
bae37d9448 | ||
![]() |
bfea0f6836 | ||
![]() |
2a64b5c880 | ||
![]() |
15bb71764e | ||
![]() |
5b70ce6be2 | ||
![]() |
22608cb44e | ||
![]() |
f1cb656f75 | ||
![]() |
db23195f4d | ||
![]() |
4a464c13a8 | ||
![]() |
a3b44bdef5 | ||
![]() |
bc16297274 | ||
![]() |
7bf3be2f07 | ||
![]() |
768d45197b | ||
![]() |
f9c17d9b5b | ||
![]() |
1f6e7de569 | ||
![]() |
2ee5fb287c | ||
![]() |
b792018c00 | ||
![]() |
063325b052 | ||
![]() |
f73b42d23f | ||
![]() |
5c67d22540 | ||
![]() |
5814526429 | ||
![]() |
0c9205aa17 | ||
![]() |
74276b400c | ||
![]() |
022650dbff | ||
![]() |
5ec0f4ebc0 | ||
![]() |
3c12c0dfd0 | ||
![]() |
bcb3f820ab | ||
![]() |
1567ea5c06 | ||
![]() |
3bb71390a9 | ||
![]() |
a3e0dada30 | ||
![]() |
81ca04fc8c | ||
![]() |
55c02f5420 | ||
![]() |
8e90dc1571 | ||
![]() |
284948d9f9 | ||
![]() |
a701f8fc7d | ||
![]() |
a97deb0c84 | ||
![]() |
d6658fe26f | ||
![]() |
7871617e9c | ||
![]() |
fdb7e5e020 | ||
![]() |
e1fc283da5 | ||
![]() |
244513fad7 | ||
![]() |
3ae7413ebd | ||
![]() |
8d210b5263 | ||
![]() |
20417b311e | ||
![]() |
aeeec16984 | ||
![]() |
1c9dddcae7 | ||
![]() |
ffcd1c0d79 | ||
![]() |
2e5177aa7c | ||
![]() |
ae3651fd3f | ||
![]() |
ddeec3512a | ||
![]() |
630585d7b4 | ||
![]() |
9105985c7c | ||
![]() |
3bf0200602 | ||
![]() |
1b9353dccc | ||
![]() |
1ee69ea214 | ||
![]() |
dcbe916286 | ||
![]() |
cc505e4881 | ||
![]() |
758ac8f850 | ||
![]() |
ff325bc19e | ||
![]() |
ddae5c4ba9 | ||
![]() |
4add839548 | ||
![]() |
3ea726f2bb | ||
![]() |
ce70a182c1 | ||
![]() |
8de7ff52e7 | ||
![]() |
dfc36aefcf | ||
![]() |
c2cb7f36a7 | ||
![]() |
e5ab1bc7a5 | ||
![]() |
4797c4e17e | ||
![]() |
0b286460b6 | ||
![]() |
a99b6084e8 | ||
![]() |
5664182f5e | ||
![]() |
68c63372c1 | ||
![]() |
ed3667c19e | ||
![]() |
caae3fa37b | ||
![]() |
066bb3e331 | ||
![]() |
98204b17a6 | ||
![]() |
096f94d3d1 | ||
![]() |
01ced3238e | ||
![]() |
8cc4c461d6 | ||
![]() |
3e8bda6a23 | ||
![]() |
7a092bea03 | ||
![]() |
54a94bd18c | ||
![]() |
a2f9b92d6c | ||
![]() |
dcc8a8cdde | ||
![]() |
c9967c20e2 | ||
![]() |
7da23aee1c | ||
![]() |
5b1be95f7d | ||
![]() |
0ff7844732 | ||
![]() |
023e7958c9 | ||
![]() |
8184226319 | ||
![]() |
94c95563ab | ||
![]() |
6ff900f3fc | ||
![]() |
8bad3cdcbc | ||
![]() |
0bb32d188c | ||
![]() |
c3b850898f | ||
![]() |
af8c5d58c8 | ||
![]() |
2db373d631 | ||
![]() |
96c72f3e06 | ||
![]() |
4bf6144daf | ||
![]() |
3e20ad6f6f | ||
![]() |
7bd3d1b892 | ||
![]() |
0bd3989bab | ||
![]() |
26c383d615 | ||
![]() |
a8b4bcdda8 | ||
![]() |
28628e5dca | ||
![]() |
ff2b1cb888 | ||
![]() |
cfe92ce845 | ||
![]() |
c95dc9d149 | ||
![]() |
d75ef7f197 | ||
![]() |
c00c0e941b | ||
![]() |
6a24510473 | ||
![]() |
72f9cd91a5 | ||
![]() |
44d443a8d6 | ||
![]() |
2e3badbf99 | ||
![]() |
6cfbb16966 | ||
![]() |
70632b088e | ||
![]() |
d46e5eca58 | ||
![]() |
05da133eb6 | ||
![]() |
c719cce4f1 | ||
![]() |
4ff63a7960 | ||
![]() |
0f96fd31bf | ||
![]() |
3d30f2ac46 | ||
![]() |
49e09a9783 | ||
![]() |
17889a3f25 | ||
![]() |
c0b708d1f3 | ||
![]() |
5c6cc3e522 | ||
![]() |
160be69852 | ||
![]() |
70714e8ca0 | ||
![]() |
4d058a5c5a | ||
![]() |
93356b39c3 | ||
![]() |
665aeeef61 | ||
![]() |
954d3f5058 | ||
![]() |
dfc3566796 | ||
![]() |
a40ead9c60 | ||
![]() |
5406772b66 | ||
![]() |
234fac40c6 | ||
![]() |
9de339e087 | ||
![]() |
bb72ea6169 | ||
![]() |
3a07f70253 | ||
![]() |
9c49863795 | ||
![]() |
9dde68f918 | ||
![]() |
4dc3093b22 | ||
![]() |
40ea3f2f54 | ||
![]() |
7ea4baed77 | ||
![]() |
0650fe509f | ||
![]() |
7d79b9e516 | ||
![]() |
1304d6361c | ||
![]() |
e156efae7d | ||
![]() |
d4649dd210 | ||
![]() |
9db275fedb | ||
![]() |
c477b2b59c | ||
![]() |
213b9d3aee | ||
![]() |
2bd609afe3 | ||
![]() |
61c9b48664 | ||
![]() |
4a04275266 | ||
![]() |
847c482c25 | ||
![]() |
6d3519419e | ||
![]() |
d1a3573c5f | ||
![]() |
fa5604ea8d | ||
![]() |
85f52e3630 | ||
![]() |
eb22036f6b | ||
![]() |
9372c988fa | ||
![]() |
1b81813223 | ||
![]() |
b70d8ef574 | ||
![]() |
5ab51271b0 | ||
![]() |
64609a0407 | ||
![]() |
750af261aa | ||
![]() |
9cd1b96e1f | ||
![]() |
f1460013be | ||
![]() |
910860ae1c | ||
![]() |
71becf0185 | ||
![]() |
572268606f | ||
![]() |
e3181aa8d4 | ||
![]() |
e7ad31c1c2 | ||
![]() |
1d59c210d2 | ||
![]() |
e0e6ea7170 | ||
![]() |
bb8a105754 | ||
![]() |
261e07dc0d | ||
![]() |
5f983a66af | ||
![]() |
c44229dd32 | ||
![]() |
0119867223 | ||
![]() |
210ec10132 | ||
![]() |
24f56b65a9 | ||
![]() |
9788798189 | ||
![]() |
dfab237830 | ||
![]() |
82e645b929 | ||
![]() |
161463deec | ||
![]() |
bba010c307 | ||
![]() |
e9c6c6c846 | ||
![]() |
5821eda98e | ||
![]() |
99827de3dd | ||
![]() |
977a8f3853 | ||
![]() |
236a5f0bf1 | ||
![]() |
50052f5115 | ||
![]() |
d775923070 | ||
![]() |
8a807af2ef | ||
![]() |
aebbfaff98 | ||
![]() |
fb16dbf7cf | ||
![]() |
e4c33d1e1d | ||
![]() |
20583ddb7d | ||
![]() |
82e0d53cdf | ||
![]() |
2361521449 | ||
![]() |
60575a1967 | ||
![]() |
0deeeb0a1d | ||
![]() |
9d9dde1065 | ||
![]() |
651ea36382 | ||
![]() |
8aa4758993 | ||
![]() |
28a46880a2 | ||
![]() |
50cfbe8b63 | ||
![]() |
52dbecb515 | ||
![]() |
b7551cbba6 | ||
![]() |
380c740ff0 | ||
![]() |
94b3e301d1 | ||
![]() |
ef9613e2d6 | ||
![]() |
43ca74549d | ||
![]() |
d093ce3d84 | ||
![]() |
7dba5e7695 | ||
![]() |
eaef05ee95 | ||
![]() |
bc86a1e53f | ||
![]() |
9b35ab37aa | ||
![]() |
a9995808ec |
740 changed files with 31900 additions and 53933 deletions
|
@ -1,2 +0,0 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
5
.clang-format
Normal file
5
.clang-format
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
ColumnLimit: 100
|
||||
...
|
|
@ -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 <your-port-name-here>"
|
||||
|
||||
# [Optional] Uncomment this section to install additional packages.
|
||||
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
||||
|
|
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
@ -0,0 +1 @@
|
|||
target
|
2
.git-blame-ignore-revs
Normal file
2
.git-blame-ignore-revs
Normal file
|
@ -0,0 +1,2 @@
|
|||
# initial bulk run of formatting with pre-commit
|
||||
93356b39c3086fdf8dd41d7357bb1c115ff69cb1
|
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
|
@ -1,3 +0,0 @@
|
|||
taskchampion/* @dbr @djmitche
|
||||
Cargo.toml @dbr @djmitche
|
||||
Cargo.lock @dbr @djmitche
|
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
|
@ -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
|
||||
|
|
2
.github/issue_template.md
vendored
2
.github/issue_template.md
vendored
|
@ -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/)
|
||||
|
|
11
.github/pull_request_template.md
vendored
11
.github/pull_request_template.md
vendored
|
@ -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.
|
79
.github/workflows/checks.yml
vendored
79
.github/workflows/checks.yml
vendored
|
@ -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"
|
||||
|
|
16
.github/workflows/docker-image.yaml
vendored
16
.github/workflows/docker-image.yaml
vendored
|
@ -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 }}
|
||||
|
|
32
.github/workflows/metadata-check.sh
vendored
Executable file
32
.github/workflows/metadata-check.sh
vendored
Executable file
|
@ -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
|
31
.github/workflows/publish-docs.yml
vendored
31
.github/workflows/publish-docs.yml
vendored
|
@ -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
|
28
.github/workflows/release-check.yaml
vendored
Normal file
28
.github/workflows/release-check.yaml
vendored
Normal file
|
@ -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
|
83
.github/workflows/rust-tests.yml
vendored
83
.github/workflows/rust-tests.yml
vendored
|
@ -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
|
103
.github/workflows/tests.yaml
vendored
103
.github/workflows/tests.yaml
vendored
|
@ -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 }}
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -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/
|
||||
|
|
4
.gitmodules
vendored
4
.gitmodules
vendored
|
@ -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
|
||||
|
|
19
.pre-commit-config.yaml
Normal file
19
.pre-commit-config.yaml
Normal file
|
@ -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
|
|
@ -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)
|
||||
|
|
1
CONTRIBUTING.md
Symbolic link
1
CONTRIBUTING.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
doc/devel/contrib/development.md
|
3187
Cargo.lock
generated
3187
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
37
Cargo.toml
37
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" }
|
||||
|
|
111
ChangeLog
111
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 -----------------------------------
|
||||
|
||||
|
|
63
INSTALL
63
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
|
||||
----------------------
|
||||
|
|
1
LICENSE
1
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.
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
<img src="https://avatars.githubusercontent.com/u/36100920?s=200&u=24da05914c20c4ccfe8485310f7b83049407fa9a&v=4"></br>
|
||||
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/actions)
|
||||
[](https://coveralls.io/github/GothenburgBitFactory/taskwarrior?branch=develop)
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/releases/latest)
|
||||
[](https://github.com/sponsors/GothenburgBitFactory/)
|
||||
[](https://gurubase.io/g/taskwarrior)
|
||||
</br>
|
||||
</div>
|
||||
|
||||
|
@ -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
|
||||
[](https://twitter.com/taskwarrior)
|
||||
[](https://reddit.com/r/taskwarrior/)
|
||||
[](https://web.libera.chat/#taskwarrior)
|
||||
[](https://discord.gg/eRXEHk8w62)
|
||||
[](https://github.com/GothenburgBitFactory/taskwarrior/discussions)
|
||||
[](https://reddit.com/r/taskwarrior/)
|
||||
|
||||
Taskwarrior has a lively community on many places on the internet.
|
||||
|
||||
|
|
1
RELEASING.md
Symbolic link
1
RELEASING.md
Symbolic link
|
@ -0,0 +1 @@
|
|||
doc/devel/contrib/releasing.md
|
13
SECURITY.md
Normal file
13
SECURITY.md
Normal file
|
@ -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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 <number-of-jobs>` 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 <number-of-jobs>
|
||||
```
|
||||
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 <regex>` or `--tests-regex <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" }
|
||||
```
|
||||
|
|
|
@ -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`
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
4
doc/man/.gitignore
vendored
4
doc/man/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
task-color.5
|
||||
task-sync.5
|
||||
task.1
|
||||
taskrc.5
|
|
@ -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 <https://taskwarrior.org>.
|
||||
|
|
|
@ -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 <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 <origin>
|
||||
.nf
|
||||
$ task config sync.server.url <url>
|
||||
$ task config sync.server.client_id <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 <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 <bucket-name>
|
||||
.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 <bucket-name>
|
||||
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
|
||||
$ task config sync.gcp.credential_path <absolute-path-to-downloaded-credentials>
|
||||
.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 <region>
|
||||
$ task config sync.aws.bucket <bucket-name>
|
||||
$ task config sync.aws.access_key_id <access-key-id>
|
||||
$ task config sync.aws.secret_access_key <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 <region>
|
||||
$ task config sync.aws.bucket <bucket-name>
|
||||
$ 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 <region>
|
||||
$ task config sync.aws.bucket <bucket-name>
|
||||
$ 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
|
||||
|
|
|
@ -24,18 +24,24 @@ descriptors), project groups, etc.
|
|||
The <filter> 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 <command> <mods>
|
||||
task 28 <command> <mods>
|
||||
task +weekend <command> <mods>
|
||||
task +bills due.by:eom <command> <mods>
|
||||
task project:Home due.before:today <command> <mods>
|
||||
task ebeeab00-ccf8-464b-8b58-f7f2d606edfb <command> <mods>
|
||||
.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]+/ )' <command> <mods>
|
||||
.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 <mods> consist of zero or more changes to apply to the selected tasks, such
|
||||
as:
|
||||
|
||||
.nf
|
||||
task <filter> <command> project:Home
|
||||
task <filter> <command> +weekend +garden due:tomorrow
|
||||
task <filter> <command> Description/annotation text
|
||||
task <filter> <command> /from/to/ <- replace first match
|
||||
task <filter> <command> /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
|
||||
<https://taskwarrior.org/tools/>:
|
||||
|
||||
.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 <filter> 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 <mods>
|
||||
|
@ -401,6 +432,15 @@ Modifies the existing task with provided information.
|
|||
.B task <filter> prepend <mods>
|
||||
Prepends description text to an existing task. Is affected by the context.
|
||||
|
||||
.TP
|
||||
.B task <filter> 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 <filter> start <mods>
|
||||
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 [<name> [<value> | '']]
|
||||
|
@ -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 <name>
|
||||
|
@ -455,7 +507,9 @@ Sets the currently active context. See the CONTEXT section.
|
|||
|
||||
Example:
|
||||
|
||||
.nf
|
||||
task context work
|
||||
.fi
|
||||
|
||||
.TP
|
||||
.B task context delete <name>
|
||||
|
@ -464,7 +518,9 @@ set as active, it will be unset.
|
|||
|
||||
Example:
|
||||
|
||||
.nf
|
||||
task context delete work
|
||||
.fi
|
||||
|
||||
.TP
|
||||
.B task context define <name> <filter>
|
||||
|
@ -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 <external command>
|
||||
|
@ -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 <filter> _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.<name>
|
||||
tw.syncneeded
|
||||
tw.program
|
||||
|
@ -670,6 +734,7 @@ from tasks, or the system. Supported DOM references are:
|
|||
system.os
|
||||
<id>.<attribute>
|
||||
<uuid>.<attribute>
|
||||
.fi
|
||||
|
||||
Note that the 'rc.<name>' 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:<project-name>
|
||||
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:<entry-date>
|
||||
For report purposes, specifies the date that a task was created.
|
||||
|
||||
.TP
|
||||
.B modified:<modified-date>
|
||||
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.<name>.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.<name>.rc.<parameter>. 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.
|
||||
|
|
|
@ -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:<directory-path>/.taskrc ...
|
||||
.RE
|
||||
.nf
|
||||
$ task rc:<directory-path>/.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.<name>:
|
||||
attribute when running task:
|
||||
|
||||
.RS
|
||||
$ task rc.<name>:<value> ...
|
||||
.RE
|
||||
.nf
|
||||
$ task rc.<name>:<value> ...
|
||||
.fi
|
||||
|
||||
or
|
||||
|
||||
.RS
|
||||
$ task rc.<name>=<value> ...
|
||||
.RE
|
||||
.nf
|
||||
$ task rc.<name>=<value> ...
|
||||
.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
|
||||
<name> = <value>
|
||||
.RE
|
||||
.nf
|
||||
<name> = <value>
|
||||
.fi
|
||||
|
||||
There may be whitespace around <name>, '=' and <value>, and it is ignored.
|
||||
Whitespace within the <value> 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 <file>
|
||||
.RE
|
||||
.nf
|
||||
include <file>
|
||||
.fi
|
||||
|
||||
There may be whitespace around 'include' and <file>. 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
|
||||
# <comment>
|
||||
.RE
|
||||
.nf
|
||||
# <comment>
|
||||
.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
|
||||
<name> =
|
||||
.RE
|
||||
.nf
|
||||
<name> =
|
||||
.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 <path/to/the/configuration/file/to/be/included>
|
||||
.RE
|
||||
.nf
|
||||
include <path/to/the/configuration/file/to/be/included>
|
||||
.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.<name>.type=string|numeric|date|duration
|
||||
.B uda.<name>.type=string|numeric|uuid|date|duration
|
||||
.RS
|
||||
Defines a UDA called '<name>', of the specified type.
|
||||
.RE
|
||||
|
|
99
doc/rc/bubblegum-256.theme
Normal file
99
doc/rc/bubblegum-256.theme
Normal file
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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=
|
||||
|
||||
|
|
|
@ -6,4 +6,3 @@ do
|
|||
echo $locale
|
||||
../../scripts/add-ons/update-holidays.pl --locale $locale --file holidays.${locale}.rc
|
||||
done
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -30,4 +30,3 @@ task rc:x add Deleted_1
|
|||
|
||||
task rc:x 14 mod depends:13
|
||||
task rc:x 15 delete
|
||||
|
||||
|
|
3
performance/.gitignore
vendored
3
performance/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
*.data
|
||||
*.rc
|
||||
export.json
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 $?;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -8,4 +8,3 @@ install (DIRECTORY add-ons
|
|||
FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE
|
||||
GROUP_READ GROUP_EXECUTE
|
||||
WORLD_READ WORLD_EXECUTE)
|
||||
|
||||
|
|
|
@ -221,4 +221,3 @@ if (open my $fh, '>:utf8', $file)
|
|||
exit 0;
|
||||
|
||||
################################################################################
|
||||
|
||||
|
|
|
@ -24,4 +24,3 @@ Expected Permissions
|
|||
Interface
|
||||
Each hook script has a unique interface. This is documented in the example
|
||||
scripts here.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -14,4 +14,3 @@ echo 'on-launch'
|
|||
# - 0: JSON ignored, non-JSON is feedback.
|
||||
# - non-0: JSON ignored, non-JSON is error.
|
||||
exit 0
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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 <jflorian@doubledog.org>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]]}"
|
||||
|
|
5
src/.gitignore
vendored
5
src/.gitignore
vendored
|
@ -1,6 +1 @@
|
|||
*.o
|
||||
Makefile.in
|
||||
debug
|
||||
calc
|
||||
lex
|
||||
liblibshared.a
|
||||
|
|
2033
src/CLI2.cpp
2033
src/CLI2.cpp
File diff suppressed because it is too large
Load diff
158
src/CLI2.h
158
src/CLI2.h
|
@ -26,100 +26,98 @@
|
|||
#ifndef INCLUDED_CLI2
|
||||
#define INCLUDED_CLI2
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <Lexer.h>
|
||||
#include <FS.h>
|
||||
#include <Lexer.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
// 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 <std::string> _tags {};
|
||||
std::map <std::string, std::string> _attributes {};
|
||||
public:
|
||||
Lexer::Type _lextype{Lexer::Type::word};
|
||||
std::vector<std::string> _tags{};
|
||||
std::map<std::string, std::string> _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 <std::string>&, 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 <std::string> getWords ();
|
||||
const std::vector <A2> 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<std::string>&, 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<std::string> getWords();
|
||||
const std::vector<A2> 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<A2>::iterator it, bool forward = true) const;
|
||||
void desugarFilterPlainArgs ();
|
||||
void insertJunctions ();
|
||||
void defaultCommand ();
|
||||
std::vector <A2> 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<A2>::iterator it, bool forward = true) const;
|
||||
void desugarFilterPlainArgs();
|
||||
void insertJunctions();
|
||||
void defaultCommand();
|
||||
std::vector<A2> lexExpression(const std::string&);
|
||||
|
||||
public:
|
||||
std::multimap <std::string, std::string> _entities {};
|
||||
std::map <std::string, std::string> _aliases {};
|
||||
std::unordered_map <int, std::string> _canonical_cache {};
|
||||
std::vector <A2> _original_args {};
|
||||
std::vector <A2> _args {};
|
||||
public:
|
||||
std::multimap<std::string, std::string> _entities{};
|
||||
std::map<std::string, std::string> _aliases{};
|
||||
std::unordered_map<int, std::string> _canonical_cache{};
|
||||
std::vector<A2> _original_args{};
|
||||
std::vector<A2> _args{};
|
||||
|
||||
std::vector <std::pair <std::string, std::string>> _id_ranges {};
|
||||
std::vector <std::string> _uuid_list {};
|
||||
std::string _command {""};
|
||||
bool _context_added {false};
|
||||
std::vector<std::pair<std::string, std::string>> _id_ranges{};
|
||||
std::vector<std::string> _uuid_list{};
|
||||
std::string _command{""};
|
||||
bool _context_added{false};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
1662
src/Context.cpp
1662
src/Context.cpp
File diff suppressed because it is too large
Load diff
149
src/Context.h
149
src/Context.h
|
@ -27,112 +27,111 @@
|
|||
#ifndef INCLUDED_CONTEXT
|
||||
#define INCLUDED_CONTEXT
|
||||
|
||||
#include <Command.h>
|
||||
#include <Column.h>
|
||||
#include <Configuration.h>
|
||||
#include <Task.h>
|
||||
#include <TDB2.h>
|
||||
#include <Hooks.h>
|
||||
#include <FS.h>
|
||||
#include <CLI2.h>
|
||||
#include <Column.h>
|
||||
#include <Command.h>
|
||||
#include <Configuration.h>
|
||||
#include <FS.h>
|
||||
#include <Hooks.h>
|
||||
#include <TDB2.h>
|
||||
#include <Task.h>
|
||||
#include <Timer.h>
|
||||
|
||||
#include <set>
|
||||
|
||||
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 <std::string> getColumns () const;
|
||||
void getLimits (int&, int&);
|
||||
const std::vector<std::string> getColumns() const;
|
||||
void getLimits(int &, int &);
|
||||
|
||||
bool color (); // TTY or <other>?
|
||||
bool verbose (const std::string&); // Verbosity control
|
||||
bool color(); // TTY or <other>?
|
||||
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 <std::string> verbosity {};
|
||||
std::vector <std::string> headers {};
|
||||
std::vector <std::string> footnotes {};
|
||||
std::vector <std::string> errors {};
|
||||
std::vector <std::string> debugMessages {};
|
||||
std::map <std::string, Command*> commands {};
|
||||
std::map <std::string, Column*> 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<std::string> verbosity{};
|
||||
std::vector<std::string> headers{};
|
||||
std::vector<std::string> footnotes{};
|
||||
std::vector<std::string> errors{};
|
||||
std::vector<std::string> debugMessages{};
|
||||
std::map<std::string, Command *> commands{};
|
||||
std::map<std::string, Column *> 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;
|
||||
|
|
554
src/DOM.cpp
554
src/DOM.cpp
|
@ -25,19 +25,22 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
#include <DOM.h>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include <stdlib.h>
|
||||
#include <Variant.h>
|
||||
#include <Lexer.h>
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <Context.h>
|
||||
#include <DOM.h>
|
||||
#include <Datetime.h>
|
||||
#include <Duration.h>
|
||||
#include <shared.h>
|
||||
#include <Lexer.h>
|
||||
#include <Variant.h>
|
||||
#include <format.h>
|
||||
#include <shared.h>
|
||||
#include <stdlib.h>
|
||||
#include <util.h>
|
||||
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// 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<int> (Context::getContext ().terminal_width
|
||||
? Context::getContext ().terminal_width
|
||||
: Context::getContext ().getWidth ()));
|
||||
} else if (name == "tw.width") {
|
||||
value = Variant(static_cast<int>(Context::getContext().terminal_width
|
||||
? Context::getContext().terminal_width
|
||||
: Context::getContext().getWidth()));
|
||||
return true;
|
||||
}
|
||||
else if (name == "tw.height")
|
||||
{
|
||||
value = Variant (static_cast<int> (Context::getContext ().terminal_height
|
||||
? Context::getContext ().terminal_height
|
||||
: Context::getContext ().getHeight ()));
|
||||
} else if (name == "tw.height") {
|
||||
value = Variant(static_cast<int>(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<int> (Context::getContext ().terminal_width
|
||||
? Context::getContext ().terminal_width
|
||||
: Context::getContext ().getWidth ()));
|
||||
} else if (name == "context.width") {
|
||||
value = Variant(static_cast<int>(Context::getContext().terminal_width
|
||||
? Context::getContext().terminal_width
|
||||
: Context::getContext().getWidth()));
|
||||
return true;
|
||||
}
|
||||
else if (name == "context.height")
|
||||
{
|
||||
value = Variant (static_cast<int> (Context::getContext ().terminal_height
|
||||
? Context::getContext ().terminal_height
|
||||
: Context::getContext ().getHeight ()));
|
||||
} else if (name == "context.height") {
|
||||
value = Variant(static_cast<int>(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<int> (task->id));
|
||||
if (task && name == "id") {
|
||||
value = Variant(static_cast<int>(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<int> (ref->id));
|
||||
if (size == 1 && canonical == "id") {
|
||||
value = Variant(static_cast<int>(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<int> (date.year ())); return true; }
|
||||
else if (elements[1] == "month") { value = Variant (static_cast<int> (date.month ())); return true; }
|
||||
else if (elements[1] == "day") { value = Variant (static_cast<int> (date.day ())); return true; }
|
||||
else if (elements[1] == "week") { value = Variant (static_cast<int> (date.week ())); return true; }
|
||||
else if (elements[1] == "weekday") { value = Variant (static_cast<int> (date.dayOfWeek ())); return true; }
|
||||
else if (elements[1] == "julian") { value = Variant (static_cast<int> (date.dayOfYear ())); return true; }
|
||||
else if (elements[1] == "hour") { value = Variant (static_cast<int> (date.hour ())); return true; }
|
||||
else if (elements[1] == "minute") { value = Variant (static_cast<int> (date.minute ())); return true; }
|
||||
else if (elements[1] == "second") { value = Variant (static_cast<int> (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<int>(date.year()));
|
||||
return true;
|
||||
} else if (elements[1] == "month") {
|
||||
value = Variant(static_cast<int>(date.month()));
|
||||
return true;
|
||||
} else if (elements[1] == "day") {
|
||||
value = Variant(static_cast<int>(date.day()));
|
||||
return true;
|
||||
} else if (elements[1] == "week") {
|
||||
value = Variant(static_cast<int>(date.week()));
|
||||
return true;
|
||||
} else if (elements[1] == "weekday") {
|
||||
value = Variant(static_cast<int>(date.dayOfWeek()));
|
||||
return true;
|
||||
} else if (elements[1] == "julian") {
|
||||
value = Variant(static_cast<int>(date.dayOfYear()));
|
||||
return true;
|
||||
} else if (elements[1] == "hour") {
|
||||
value = Variant(static_cast<int>(date.hour()));
|
||||
return true;
|
||||
} else if (elements[1] == "minute") {
|
||||
value = Variant(static_cast<int>(date.minute()));
|
||||
return true;
|
||||
} else if (elements[1] == "second") {
|
||||
value = Variant(static_cast<int>(date.second()));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (size == 2 && elements[0] == "annotations" && elements[1] == "count")
|
||||
{
|
||||
value = Variant (static_cast<int> (ref->getAnnotationCount ()));
|
||||
if (size == 2 && elements[0] == "annotations" && elements[1] == "count") {
|
||||
value = Variant(static_cast<int>(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) {
|
||||
// <annotations>.<N>.entry.year
|
||||
// <annotations>.<N>.entry.month
|
||||
// <annotations>.<N>.entry.day
|
||||
|
@ -450,22 +408,41 @@ bool getDOM (const std::string& name, const Task* task, Variant& value)
|
|||
// <annotations>.<N>.entry.hour
|
||||
// <annotations>.<N>.entry.minute
|
||||
// <annotations>.<N>.entry.second
|
||||
Datetime date (i.first.substr (11));
|
||||
if (elements[3] == "year") { value = Variant (static_cast<int> (date.year ())); return true; }
|
||||
else if (elements[3] == "month") { value = Variant (static_cast<int> (date.month ())); return true; }
|
||||
else if (elements[3] == "day") { value = Variant (static_cast<int> (date.day ())); return true; }
|
||||
else if (elements[3] == "week") { value = Variant (static_cast<int> (date.week ())); return true; }
|
||||
else if (elements[3] == "weekday") { value = Variant (static_cast<int> (date.dayOfWeek ())); return true; }
|
||||
else if (elements[3] == "julian") { value = Variant (static_cast<int> (date.dayOfYear ())); return true; }
|
||||
else if (elements[3] == "hour") { value = Variant (static_cast<int> (date.hour ())); return true; }
|
||||
else if (elements[3] == "minute") { value = Variant (static_cast<int> (date.minute ())); return true; }
|
||||
else if (elements[3] == "second") { value = Variant (static_cast<int> (date.second ())); return true; }
|
||||
Datetime date(i.first.substr(11));
|
||||
if (elements[3] == "year") {
|
||||
value = Variant(static_cast<int>(date.year()));
|
||||
return true;
|
||||
} else if (elements[3] == "month") {
|
||||
value = Variant(static_cast<int>(date.month()));
|
||||
return true;
|
||||
} else if (elements[3] == "day") {
|
||||
value = Variant(static_cast<int>(date.day()));
|
||||
return true;
|
||||
} else if (elements[3] == "week") {
|
||||
value = Variant(static_cast<int>(date.week()));
|
||||
return true;
|
||||
} else if (elements[3] == "weekday") {
|
||||
value = Variant(static_cast<int>(date.dayOfWeek()));
|
||||
return true;
|
||||
} else if (elements[3] == "julian") {
|
||||
value = Variant(static_cast<int>(date.dayOfYear()));
|
||||
return true;
|
||||
} else if (elements[3] == "hour") {
|
||||
value = Variant(static_cast<int>(date.hour()));
|
||||
return true;
|
||||
} else if (elements[3] == "minute") {
|
||||
value = Variant(static_cast<int>(date.minute()));
|
||||
return true;
|
||||
} else if (elements[3] == "second") {
|
||||
value = Variant(static_cast<int>(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 <std::string> DOM::decomposeReference (const std::string& reference)
|
||||
{
|
||||
return split (reference, '.');
|
||||
std::vector<std::string> 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();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
69
src/DOM.h
69
src/DOM.h
|
@ -27,49 +27,48 @@
|
|||
#ifndef INCLUDED_DOM
|
||||
#define INCLUDED_DOM
|
||||
|
||||
#include <string>
|
||||
#include <Variant.h>
|
||||
#include <Task.h>
|
||||
#include <Variant.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
// 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 <std::string> 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<std::string> 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 <DOM::Node*> _branches {};
|
||||
public:
|
||||
std::string _name{"Unknown"};
|
||||
bool (*_provider)(const std::string&, Variant&){nullptr};
|
||||
std::vector<DOM::Node*> _branches{};
|
||||
};
|
||||
|
||||
private:
|
||||
DOM::Node* _node {nullptr};
|
||||
private:
|
||||
DOM::Node* _node{nullptr};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
814
src/Eval.cpp
814
src/Eval.cpp
File diff suppressed because it is too large
Load diff
71
src/Eval.h
71
src/Eval.h
|
@ -27,50 +27,51 @@
|
|||
#ifndef INCLUDED_EVAL
|
||||
#define INCLUDED_EVAL
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <Lexer.h>
|
||||
#include <Variant.h>
|
||||
|
||||
bool domSource (const std::string&, Variant&);
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 <std::pair <std::string, Lexer::Type>>&);
|
||||
void evaluateCompiledExpression (Variant&);
|
||||
void debug (bool);
|
||||
class Eval {
|
||||
public:
|
||||
Eval();
|
||||
|
||||
static std::vector <std::string> getOperators ();
|
||||
static std::vector <std::string> 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<std::pair<std::string, Lexer::Type>> &);
|
||||
void evaluateCompiledExpression(Variant &);
|
||||
void debug(bool);
|
||||
|
||||
private:
|
||||
void evaluatePostfixStack (const std::vector <std::pair <std::string, Lexer::Type>>&, Variant&) const;
|
||||
void infixToPostfix (std::vector <std::pair <std::string, Lexer::Type>>&) const;
|
||||
void infixParse (std::vector <std::pair <std::string, Lexer::Type>>&) const;
|
||||
bool parseLogical (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseRegex (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseEquality (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseComparative (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseArithmetic (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseGeometric (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseTag (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseUnary (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parseExponent (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool parsePrimitive (std::vector <std::pair <std::string, Lexer::Type>>&, unsigned int &) const;
|
||||
bool identifyOperator (const std::string&, char&, unsigned int&, char&) const;
|
||||
static std::vector<std::string> getOperators();
|
||||
static std::vector<std::string> getBinaryOperators();
|
||||
|
||||
std::string dump (std::vector <std::pair <std::string, Lexer::Type>>&) const;
|
||||
private:
|
||||
void evaluatePostfixStack(const std::vector<std::pair<std::string, Lexer::Type>> &,
|
||||
Variant &) const;
|
||||
void infixToPostfix(std::vector<std::pair<std::string, Lexer::Type>> &) const;
|
||||
void infixParse(std::vector<std::pair<std::string, Lexer::Type>> &) const;
|
||||
bool parseLogical(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseRegex(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseEquality(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseComparative(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseArithmetic(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseGeometric(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseTag(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseUnary(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parseExponent(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool parsePrimitive(std::vector<std::pair<std::string, Lexer::Type>> &, unsigned int &) const;
|
||||
bool identifyOperator(const std::string &, char &, unsigned int &, char &) const;
|
||||
|
||||
private:
|
||||
std::vector <bool (*)(const std::string&, Variant&)> _sources {};
|
||||
bool _debug {false};
|
||||
std::vector <std::pair <std::string, Lexer::Type>> _compiled {};
|
||||
std::string dump(std::vector<std::pair<std::string, Lexer::Type>> &) const;
|
||||
|
||||
private:
|
||||
std::vector<bool (*)(const std::string &, Variant &)> _sources{};
|
||||
bool _debug{false};
|
||||
std::vector<std::pair<std::string, Lexer::Type>> _compiled{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
249
src/Filter.cpp
249
src/Filter.cpp
|
@ -25,145 +25,130 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
#include <Filter.h>
|
||||
#include <algorithm>
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <Context.h>
|
||||
#include <Timer.h>
|
||||
#include <DOM.h>
|
||||
#include <Eval.h>
|
||||
#include <Filter.h>
|
||||
#include <Timer.h>
|
||||
#include <Variant.h>
|
||||
#include <format.h>
|
||||
#include <shared.h>
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Take an input set of tasks and filter into a subset.
|
||||
void Filter::subset (const std::vector <Task>& input, std::vector <Task>& output)
|
||||
{
|
||||
void Filter::subset(const std::vector<Task>& input, std::vector<Task>& output) {
|
||||
Timer timer;
|
||||
_startCount = (int) input.size ();
|
||||
_startCount = (int)input.size();
|
||||
|
||||
Context::getContext ().cli2.prepareFilter ();
|
||||
Context::getContext().cli2.prepareFilter();
|
||||
|
||||
std::vector <std::pair <std::string, Lexer::Type>> precompiled;
|
||||
for (auto& a : Context::getContext ().cli2._args)
|
||||
if (a.hasTag ("FILTER"))
|
||||
precompiled.emplace_back (a.getToken (), a._lextype);
|
||||
std::vector<std::pair<std::string, Lexer::Type>> 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 <Task>& output)
|
||||
{
|
||||
void Filter::subset(std::vector<Task>& output) {
|
||||
Timer timer;
|
||||
Context::getContext ().cli2.prepareFilter ();
|
||||
Context::getContext().cli2.prepareFilter();
|
||||
|
||||
std::vector <std::pair <std::string, Lexer::Type>> precompiled;
|
||||
for (auto& a : Context::getContext ().cli2._args)
|
||||
if (a.hasTag ("FILTER"))
|
||||
precompiled.emplace_back (a.getToken (), a._lextype);
|
||||
std::vector<std::pair<std::string, Lexer::Type>> 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; }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
32
src/Filter.h
32
src/Filter.h
|
@ -27,28 +27,26 @@
|
|||
#ifndef INCLUDED_FILTER
|
||||
#define INCLUDED_FILTER
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Task.h>
|
||||
#include <Variant.h>
|
||||
|
||||
class Filter
|
||||
{
|
||||
public:
|
||||
Filter () = default;
|
||||
#include <vector>
|
||||
|
||||
void subset (const std::vector <Task>&, std::vector <Task>&);
|
||||
void subset (std::vector <Task>&);
|
||||
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<Task>&, std::vector<Task>&);
|
||||
void subset(std::vector<Task>&);
|
||||
bool hasFilter() const;
|
||||
bool pendingOnly() const;
|
||||
void safety() const;
|
||||
void disableSafety();
|
||||
|
||||
private:
|
||||
int _startCount{0};
|
||||
int _endCount{0};
|
||||
bool _safety{true};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
|
|
534
src/Hooks.cpp
534
src/Hooks.cpp
|
@ -25,91 +25,85 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <Hooks.h>
|
||||
|
||||
#include <algorithm>
|
||||
// If <iostream> is included, put it after <stdio.h>, because it includes
|
||||
// <stdio.h>, and therefore would ignore the _WITH_GETLINE.
|
||||
#ifdef FREEBSD
|
||||
#define _WITH_GETLINE
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sys/types.h>
|
||||
#include <Context.h>
|
||||
#include <Variant.h>
|
||||
#include <DOM.h>
|
||||
#include <Lexer.h>
|
||||
#include <JSON.h>
|
||||
#include <Timer.h>
|
||||
#include <FS.h>
|
||||
#include <JSON.h>
|
||||
#include <Lexer.h>
|
||||
#include <Timer.h>
|
||||
#include <Variant.h>
|
||||
#include <format.h>
|
||||
#include <shared.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <util.h>
|
||||
|
||||
#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 <rc.hooks.location>
|
||||
// <rc.data.location>/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 <std::string> matchingScripts = scripts ("on-launch");
|
||||
if (matchingScripts.size ())
|
||||
{
|
||||
for (auto& script : matchingScripts)
|
||||
{
|
||||
std::vector <std::string> input;
|
||||
std::vector <std::string> output;
|
||||
int status = callHookScript (script, input, output);
|
||||
std::vector<std::string> matchingScripts = scripts("on-launch");
|
||||
if (matchingScripts.size()) {
|
||||
for (auto& script : matchingScripts) {
|
||||
std::vector<std::string> input;
|
||||
std::vector<std::string> output;
|
||||
int status = callHookScript(script, input, output);
|
||||
|
||||
std::vector <std::string> outputJSON;
|
||||
std::vector <std::string> outputFeedback;
|
||||
separateOutput (output, outputJSON, outputFeedback);
|
||||
std::vector<std::string> outputJSON;
|
||||
std::vector<std::string> 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 <std::string> matchingScripts = scripts ("on-exit");
|
||||
if (matchingScripts.size ())
|
||||
{
|
||||
std::vector<std::string> matchingScripts = scripts("on-exit");
|
||||
if (matchingScripts.size()) {
|
||||
// Get the set of changed tasks.
|
||||
std::vector <Task> tasks;
|
||||
Context::getContext ().tdb2.get_changes (tasks);
|
||||
std::vector<Task> tasks;
|
||||
Context::getContext().tdb2.get_changes(tasks);
|
||||
|
||||
// Convert to a vector of strings.
|
||||
std::vector <std::string> input;
|
||||
std::vector<std::string> 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 <std::string> output;
|
||||
int status = callHookScript (script, input, output);
|
||||
for (auto& script : matchingScripts) {
|
||||
std::vector<std::string> output;
|
||||
int status = callHookScript(script, input, output);
|
||||
|
||||
std::vector <std::string> outputJSON;
|
||||
std::vector <std::string> outputFeedback;
|
||||
separateOutput (output, outputJSON, outputFeedback);
|
||||
std::vector<std::string> outputJSON;
|
||||
std::vector<std::string> 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 <std::string> matchingScripts = scripts ("on-add");
|
||||
if (matchingScripts.size ())
|
||||
{
|
||||
std::vector<std::string> matchingScripts = scripts("on-add");
|
||||
if (matchingScripts.size()) {
|
||||
// Convert task to a vector of strings.
|
||||
std::vector <std::string> input;
|
||||
input.push_back (task.composeJSON ());
|
||||
std::vector<std::string> input;
|
||||
input.push_back(task.composeJSON());
|
||||
|
||||
// Call the hook scripts.
|
||||
for (auto& script : matchingScripts)
|
||||
{
|
||||
std::vector <std::string> output;
|
||||
int status = callHookScript (script, input, output);
|
||||
for (auto& script : matchingScripts) {
|
||||
std::vector<std::string> output;
|
||||
int status = callHookScript(script, input, output);
|
||||
|
||||
std::vector <std::string> outputJSON;
|
||||
std::vector <std::string> outputFeedback;
|
||||
separateOutput (output, outputJSON, outputFeedback);
|
||||
std::vector<std::string> outputJSON;
|
||||
std::vector<std::string> 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 <std::string> matchingScripts = scripts ("on-modify");
|
||||
if (matchingScripts.size ())
|
||||
{
|
||||
std::vector<std::string> matchingScripts = scripts("on-modify");
|
||||
if (matchingScripts.size()) {
|
||||
// Convert vector of tasks to a vector of strings.
|
||||
std::vector <std::string> input;
|
||||
input.push_back (before.composeJSON ()); // [line 0] original, never changes
|
||||
input.push_back (after.composeJSON ()); // [line 1] modified
|
||||
std::vector<std::string> 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 <std::string> output;
|
||||
int status = callHookScript (script, input, output);
|
||||
for (auto& script : matchingScripts) {
|
||||
std::vector<std::string> output;
|
||||
int status = callHookScript(script, input, output);
|
||||
|
||||
std::vector <std::string> outputJSON;
|
||||
std::vector <std::string> outputFeedback;
|
||||
separateOutput (output, outputJSON, outputFeedback);
|
||||
std::vector<std::string> outputJSON;
|
||||
std::vector<std::string> 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 <std::string> Hooks::list () const
|
||||
{
|
||||
return _scripts;
|
||||
}
|
||||
std::vector<std::string> Hooks::list() const { return _scripts; }
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
std::vector <std::string> Hooks::scripts (const std::string& event) const
|
||||
{
|
||||
std::vector <std::string> 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<std::string> Hooks::scripts(const std::string& event) const {
|
||||
std::vector<std::string> 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 <std::string> Hooks::scripts (const std::string& event) const
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
void Hooks::separateOutput (
|
||||
const std::vector <std::string>& output,
|
||||
std::vector <std::string>& json,
|
||||
std::vector <std::string>& feedback) const
|
||||
{
|
||||
for (auto& i : output)
|
||||
{
|
||||
if (isJSON (i))
|
||||
json.push_back (i);
|
||||
void Hooks::separateOutput(const std::vector<std::string>& output, std::vector<std::string>& json,
|
||||
std::vector<std::string>& 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 <std::string>& 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<std::string>& 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 <std::string>& 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<std::string>& 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 <std::string>& input,
|
||||
const Task& task,
|
||||
const std::string& script) const
|
||||
{
|
||||
std::string uuid = task.get ("uuid");
|
||||
void Hooks::assertSameTask(const std::vector<std::string>& 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 <std::string>& input,
|
||||
const std::string& script) const
|
||||
{
|
||||
void Hooks::assertFeedback(const std::vector<std::string>& 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 <std::string>& Hooks::buildHookScriptArgs (std::vector <std::string>& args) const
|
||||
{
|
||||
std::vector<std::string>& Hooks::buildHookScriptArgs(std::vector<std::string>& 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 <std::string>& input,
|
||||
std::vector <std::string>& output) const
|
||||
{
|
||||
if (_debug >= 1)
|
||||
Context::getContext ().debug ("Hook: Calling " + script);
|
||||
int Hooks::callHookScript(const std::string& script, const std::vector<std::string>& input,
|
||||
std::vector<std::string>& 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 <std::string> args;
|
||||
buildHookScriptArgs (args);
|
||||
if (_debug >= 2)
|
||||
{
|
||||
Context::getContext ().debug ("Hooks: args");
|
||||
for (const auto& arg: args)
|
||||
Context::getContext ().debug (" " + arg);
|
||||
std::vector<std::string> 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;
|
||||
|
|
56
src/Hooks.h
56
src/Hooks.h
|
@ -27,37 +27,39 @@
|
|||
#ifndef INCLUDED_HOOKS
|
||||
#define INCLUDED_HOOKS
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <Task.h>
|
||||
|
||||
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 <std::string> list () const;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
private:
|
||||
std::vector <std::string> scripts (const std::string&) const;
|
||||
void separateOutput (const std::vector <std::string>&, std::vector <std::string>&, std::vector <std::string>&) const;
|
||||
bool isJSON (const std::string&) const;
|
||||
void assertValidJSON (const std::vector <std::string>&, const std::string&) const;
|
||||
void assertNTasks (const std::vector <std::string>&, unsigned int, const std::string&) const;
|
||||
void assertSameTask (const std::vector <std::string>&, const Task&, const std::string&) const;
|
||||
void assertFeedback (const std::vector <std::string>&, const std::string&) const;
|
||||
std::vector <std::string>& buildHookScriptArgs (std::vector <std::string>&) const;
|
||||
int callHookScript (const std::string&, const std::vector <std::string>&, std::vector <std::string>&) 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<std::string> list() const;
|
||||
|
||||
private:
|
||||
bool _enabled {true};
|
||||
int _debug {0};
|
||||
std::vector <std::string> _scripts {};
|
||||
private:
|
||||
std::vector<std::string> scripts(const std::string&) const;
|
||||
void separateOutput(const std::vector<std::string>&, std::vector<std::string>&,
|
||||
std::vector<std::string>&) const;
|
||||
bool isJSON(const std::string&) const;
|
||||
void assertValidJSON(const std::vector<std::string>&, const std::string&) const;
|
||||
void assertNTasks(const std::vector<std::string>&, unsigned int, const std::string&) const;
|
||||
void assertSameTask(const std::vector<std::string>&, const Task&, const std::string&) const;
|
||||
void assertFeedback(const std::vector<std::string>&, const std::string&) const;
|
||||
std::vector<std::string>& buildHookScriptArgs(std::vector<std::string>&) const;
|
||||
int callHookScript(const std::string&, const std::vector<std::string>&,
|
||||
std::vector<std::string>&) const;
|
||||
|
||||
private:
|
||||
bool _enabled{true};
|
||||
int _debug{0};
|
||||
std::vector<std::string> _scripts{};
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
1305
src/Lexer.cpp
1305
src/Lexer.cpp
File diff suppressed because it is too large
Load diff
161
src/Lexer.h
161
src/Lexer.h
|
@ -27,95 +27,108 @@
|
|||
#ifndef INCLUDED_LEXER
|
||||
#define INCLUDED_LEXER
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 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 <std::string, std::string> attributes;
|
||||
static std::map<std::string, std::string> 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 <std::string> 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<std::string> 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 <std::string>&, bool, bool);
|
||||
bool isOneOf (const std::map <std::string, std::string>&, 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<std::string>&, bool, bool);
|
||||
bool isOneOf(const std::map<std::string, std::string>&, bool, bool);
|
||||
|
||||
private:
|
||||
private:
|
||||
std::string _text;
|
||||
std::size_t _cursor;
|
||||
std::size_t _eos;
|
||||
|
|
|
@ -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 <cmake.h>
|
||||
#include <format.h>
|
||||
#include <assert.h>
|
||||
#include "tc/Replica.h"
|
||||
#include "tc/Task.h"
|
||||
// cmake.h include header must come first
|
||||
|
||||
using namespace tc::ffi;
|
||||
#include <Operation.h>
|
||||
#include <taskchampion-cpp/lib.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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> Operation::operations(const rust::Vec<tc::Operation>& 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
89
src/Operation.h
Normal file
89
src/Operation.h
Normal file
|
@ -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 <taskchampion-cpp/lib.h>
|
||||
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
// 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<Operation> operations(const rust::Vec<tc::Operation> &);
|
||||
|
||||
// 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<std::string> get_value() const {
|
||||
std::optional<std::string> value{std::string()};
|
||||
if (!op->get_value(value.value())) {
|
||||
value = std::nullopt;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
std::optional<std::string> get_old_value() const {
|
||||
std::optional<std::string> 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<time_t>(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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
582
src/TDB2.cpp
582
src/TDB2.cpp
|
@ -25,109 +25,71 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <cmake.h>
|
||||
#include <TDB2.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_set>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <Context.h>
|
||||
// cmake.h include header must come first
|
||||
|
||||
#include <Color.h>
|
||||
#include <Context.h>
|
||||
#include <Datetime.h>
|
||||
#include <TDB2.h>
|
||||
#include <Table.h>
|
||||
#include <shared.h>
|
||||
#include <format.h>
|
||||
#include <main.h>
|
||||
#include <shared.h>
|
||||
#include <stdlib.h>
|
||||
#include <util.h>
|
||||
#include "tc/Server.h"
|
||||
#include "tc/util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
bool TDB2::debug_mode = false;
|
||||
static void dependency_scan (std::vector<Task> &);
|
||||
static void dependency_scan(std::vector<Task>&);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
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<tc::Operation> 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<tc::Operation> 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<std::string> 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<std::string>(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<tc::Operation> 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 <Task>& changes)
|
||||
{
|
||||
rust::Box<tc::Replica>& TDB2::replica() {
|
||||
// One of the open_replica_ methods must be called before this one.
|
||||
assert(_replica);
|
||||
return _replica.value();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const rust::Box<tc::WorkingSet>& 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<tc::Operation>& 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<Task>& changes) {
|
||||
std::map<std::string, Task>& 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 <Task>& 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 == "" ? "<empty>" : 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 <Task> TDB2::all_tasks ()
|
||||
{
|
||||
auto all_tctasks = replica.all_tasks();
|
||||
std::vector <Task> all;
|
||||
for (auto& tctask : all_tctasks)
|
||||
all.push_back (Task (std::move (tctask)));
|
||||
const std::vector<Task> TDB2::all_tasks() {
|
||||
Timer timer;
|
||||
auto all_tctasks = replica()->all_task_data();
|
||||
std::vector<Task> 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 <Task> TDB2::pending_tasks ()
|
||||
{
|
||||
const tc::WorkingSet &ws = working_set ();
|
||||
auto largest_index = ws.largest_index ();
|
||||
const std::vector<Task> TDB2::pending_tasks() {
|
||||
if (!_pending_tasks) {
|
||||
Timer timer;
|
||||
|
||||
std::vector <Task> 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<Task> 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 <Task> TDB2::completed_tasks ()
|
||||
{
|
||||
auto all_tctasks = replica.all_tasks();
|
||||
const tc::WorkingSet &ws = working_set ();
|
||||
const std::vector<Task> TDB2::completed_tasks() {
|
||||
if (!_completed_tasks) {
|
||||
auto all_tctasks = replica()->all_task_data();
|
||||
auto& ws = working_set();
|
||||
|
||||
std::vector <Task> 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<Task> 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<std::string>(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<Task> 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<Task> 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<std::string>(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 <Task> TDB2::siblings (Task& task)
|
||||
{
|
||||
std::vector <Task> results;
|
||||
if (task.has ("parent"))
|
||||
{
|
||||
std::string parent = task.get ("parent");
|
||||
const std::vector<Task> TDB2::siblings(Task& task) {
|
||||
std::vector<Task> 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 <Task> TDB2::siblings (Task& task)
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
const std::vector <Task> TDB2::children (Task& parent)
|
||||
{
|
||||
const std::vector<Task> TDB2::children(Task& parent) {
|
||||
// scan _pending_ tasks for those with `parent` equal to this task
|
||||
std::vector <Task> results;
|
||||
std::string this_uuid = parent.get ("uuid");
|
||||
std::vector<Task> 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<std::string>(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<Task> &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<Task>& 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;
|
||||
}
|
||||
|
|
85
src/TDB2.h
85
src/TDB2.h
|
@ -27,69 +27,66 @@
|
|||
#ifndef INCLUDED_TDB2
|
||||
#define INCLUDED_TDB2
|
||||
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <stdio.h>
|
||||
#include <FS.h>
|
||||
#include <Task.h>
|
||||
#include <tc/WorkingSet.h>
|
||||
#include <tc/Replica.h>
|
||||
#include <taskchampion-cpp/lib.h>
|
||||
|
||||
namespace tc {
|
||||
class Server;
|
||||
}
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// 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 <Task>&);
|
||||
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<Task> &);
|
||||
void gc();
|
||||
void expire_tasks();
|
||||
int latest_id();
|
||||
|
||||
// Generalized task accessors.
|
||||
const std::vector <Task> all_tasks ();
|
||||
const std::vector <Task> pending_tasks ();
|
||||
const std::vector <Task> completed_tasks ();
|
||||
bool get (int, Task&);
|
||||
bool get (const std::string&, Task&);
|
||||
bool has (const std::string&);
|
||||
const std::vector <Task> siblings (Task&);
|
||||
const std::vector <Task> children (Task&);
|
||||
const std::vector<Task> all_tasks();
|
||||
const std::vector<Task> pending_tasks();
|
||||
const std::vector<Task> completed_tasks();
|
||||
bool get(int, Task &);
|
||||
bool get(const std::string &, Task &);
|
||||
bool has(const std::string &);
|
||||
const std::vector<Task> siblings(Task &);
|
||||
const std::vector<Task> 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<tc::Replica> &replica();
|
||||
|
||||
void sync (tc::Server server, bool avoid_snapshots);
|
||||
bool confirm_revert(struct tc::ffi::TCReplicaOpList);
|
||||
private:
|
||||
std::optional<rust::Box<tc::Replica>> _replica;
|
||||
|
||||
private:
|
||||
tc::Replica replica;
|
||||
std::optional<tc::WorkingSet> _working_set;
|
||||
// Cached information from the replica
|
||||
std::optional<rust::Box<tc::WorkingSet>> _working_set;
|
||||
std::optional<std::vector<Task>> _pending_tasks;
|
||||
std::optional<std::vector<Task>> _completed_tasks;
|
||||
void invalidate_cached_info();
|
||||
|
||||
// UUID -> Task containing all tasks modified in this invocation.
|
||||
std::map<std::string, Task> 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<tc::WorkingSet> &working_set();
|
||||
void maybe_add_undo_point(rust::Vec<tc::Operation> &);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
178
src/TF2.cpp
Normal file
178
src/TF2.cpp
Normal file
|
@ -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 <Color.h>
|
||||
#include <Context.h>
|
||||
#include <Datetime.h>
|
||||
#include <TF2.h>
|
||||
#include <Table.h>
|
||||
#include <cmake.h>
|
||||
#include <format.h>
|
||||
#ifdef PRODUCT_TASKWARRIOR
|
||||
#include <legacy.h>
|
||||
#endif
|
||||
#include <shared.h>
|
||||
#include <util.h>
|
||||
|
||||
#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<std::map<std::string, std::string>>& 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<std::string, std::string> TF2::load_task(const std::string& input) {
|
||||
std::map<std::string, std::string> 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
|
63
src/TF2.h
Normal file
63
src/TF2.h
Normal file
|
@ -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 <FS.h>
|
||||
#include <Task.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// 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<std::map<std::string, std::string>>& get_tasks();
|
||||
|
||||
std::map<std::string, std::string> 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<std::map<std::string, std::string>> _tasks;
|
||||
std::vector<std::string> _lines;
|
||||
File _file;
|
||||
};
|
||||
|
||||
#endif
|
||||
////////////////////////////////////////////////////////////////////////////////
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue